diff --git a/configure.ac b/configure.ac
index f3ccec86a58e237507eef44170e90f407afb0ca3..7cb1ae12aa845eb885d46bf758a359bf0dc03855 100644
--- a/configure.ac
+++ b/configure.ac
@@ -432,6 +432,20 @@ elif test "$stars_density_checks" != "no"; then
    AC_DEFINE_UNQUOTED([SWIFT_STARS_DENSITY_CHECKS], [$enableval] ,[Enable stars density brute-force checks])
 fi
 
+# Check if sink density checks are on for some particles.
+AC_ARG_ENABLE([sink-density-checks],
+   [AS_HELP_STRING([--enable-sink-density-checks],
+     [Activate expensive brute-force sink density checks for a fraction 1/N of all particles @<:@N@:>@]
+   )],
+   [sink_density_checks="$enableval"],
+   [sink_density_checks="no"]
+)
+if test "$sink_density_checks" = "yes"; then
+   AC_MSG_ERROR(Need to specify the fraction of particles to check when using --enable-sink-density-checks!)
+elif test "$sink_density_checks" != "no"; then
+   AC_DEFINE_UNQUOTED([SWIFT_SINK_DENSITY_CHECKS], [$enableval] ,[Enable sink density brute-force checks])
+fi
+
 # Check if ghost statistics are enabled
 AC_ARG_ENABLE([ghost-statistics],
    [AS_HELP_STRING([--enable-ghost-statistics],
@@ -2864,6 +2878,9 @@ case "$with_sink" in
    none)
       AC_DEFINE([SINK_NONE], [1], [No sink particle model])
    ;;
+   Basic)
+    AC_DEFINE([SINK_BASIC], [1], [Simple, self-contained sink model with only Bondi-Hoyle accretion and no SF.])
+    ;;
    GEAR)
     AC_DEFINE([SINK_GEAR], [1], [GEAR sink particle model])
     ;;
diff --git a/doc/RTD/source/ExternalPotentials/index.rst b/doc/RTD/source/ExternalPotentials/index.rst
index 14537920850d8aa513f7269b62f8c09608c38dfa..a36025691732b41204cc2b1309d751146d1f1a17 100644
--- a/doc/RTD/source/ExternalPotentials/index.rst
+++ b/doc/RTD/source/ExternalPotentials/index.rst
@@ -374,7 +374,7 @@ The parameters of the model are:
       Mdisk_Msun:       6.8e10                 # Mass of the disk (in M_sun)
       Rdisk_kpc:        3.0                    # Effective radius of the disk (in kpc)
       Zdisk_kpc:        0.280                  # Scale-height of the disk (in kpc)
-      amplitude:        1.0                    # Amplitude of the bulge
+      amplitude_Msun_per_kpc3: 1.0e10          # Amplitude of the bulge (in M_sun/kpc^3)
       r_1_kpc:          1.0                    # Reference radius for amplitude of the bulge (in kpc)
       alpha:            1.8                    # Exponent of the power law of the bulge
       r_c_kpc:          1.9                    # Cut-off radius of the bulge (in kpc)
diff --git a/doc/RTD/source/SubgridModels/Basic/index.rst b/doc/RTD/source/SubgridModels/Basic/index.rst
index ea193ef9eb228969eeee5a9fdd387193a373ec4f..710da1371541ad8bd24bf12fd9187a91ba419474 100644
--- a/doc/RTD/source/SubgridModels/Basic/index.rst
+++ b/doc/RTD/source/SubgridModels/Basic/index.rst
@@ -5,6 +5,25 @@
 Basic model (others)
 ====================
 
+Sinks: Simple Bondi-Hoyle accretion
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+The ``Basic`` sink model provides a foundation on which new sink implementations could be built. It includes a prescription for Bondi-Hoyle gas accretion, and a method for sink-sink mergers that is a slightly simplified version of the implementation used in GEAR.
+
+No other physics is implemented for this model. Sinks cannot form - to use this model, sink particles must already be present in the initial conditions. They also cannot spawn stars from the gas they accrete.
+
+Bondi-Hoyle accretion can be done in one of two ways:
+
+ * Gas particles within the sink's kernel are stochastically swallowed entirely, with a probability set by the Bondi-Hoyle rate. Specifically, the probability is set by the current difference between the sink's subgrid mass (determined by the accretion rate) and its dynamical mass (which tracks the number of particles/sinks actually swallowed). This mode is equivalent to the EAGLE black hole accretion model.
+ * Gas particles within the sink's kernel are "nibbled" down to some minimal mass, which can be specified by the user. This method is equivalent to the black hole accretion model of Bahe et al. 2022.
+
+This model has only two parameters that must be specified in your parameter ``yml`` file:
+
+ * ``BasicSink:use_nibbling``: determines whether accretion is done by "nibbling" or by swallowing outright.
+ * ``BasicSink:min_gas_mass_for_nibbling_Msun``: if using "nibbling", the minimum mass to which gas particles can be nibbled. A good default is half the original particle mass.
+
+For an even more bare-bones starting point, the ``Default`` sink model contains no physics at all, and is a totally blank canvas on which to build your sink model.
+
 
 Cooling: Analytic models
 ~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/doc/RTD/source/SubgridModels/GEAR/sinks/params.rst b/doc/RTD/source/SubgridModels/GEAR/sinks/params.rst
index 9f8409de7e95f48099c09684a618a07a6853b33e..e542d867cebaf71599ee90e0bcf3e6ba9fe5d58f 100644
--- a/doc/RTD/source/SubgridModels/GEAR/sinks/params.rst
+++ b/doc/RTD/source/SubgridModels/GEAR/sinks/params.rst
@@ -10,12 +10,17 @@ Model parameters
 
 The parameters of the GEAR sink model are grouped into the ``GEARSink`` section of the parameter file. 
 
-The first two parameters are:
+The first three parameters are:
 
-* The sink cut-off radius for gas and sink accretion: ``cut_off_radius``,
+* Whether we are using a fixed cut-off radius for gas and sink accretion: ``use_fixed_cut_off_radius``,
+* The sink cut-off radius: ``cut_off_radius``,
 * The sink inner accretion radius fraction in terms of the cut-off radius: ``f_acc``.
 
-The ``f_acc`` parameter is optional. Its default value is :math:`0.1`. Its value must respect :math:`0 \leq f_\text{acc} \leq 1` . It describes the inner radius :math:`f_{\text{acc}} \cdot r_{\text{cut-off}}` in which gas particles are swallowed without any further checks, as explained below.
+The ``use_fixed_cut_off_radius`` is mandatory and should be set to 0 or 1. If set to 1, the GEAR model will use a fixed cutoff radius equal to the value of ``cut_off_radius``. If not, the cutoff radius is allowed to vary according to the local gas density. In the code, the cutoff radius is always equal to the sink's smoothing length multiplied by a constant factor ``kernel_gamma`` - setting a fixed cutoff will fix the smoothing length at the appropriate value for all sinks.
+
+The ``cut_off_radius`` parameter is optional, and is ignored if ``use_fixed_cut_off_radius`` is 0. If a fixed cut-off is used, every sink's smoothing length will be permanently set to this number divided by ``kernel_gamma`` on formation.
+
+The ``f_acc`` parameter is also optional. Its default value is :math:`0.1`. Its value must respect :math:`0 \leq f_\text{acc} \leq 1` . It describes the inner radius :math:`f_{\text{acc}} \cdot r_{\text{cut-off}}` in which gas particles are swallowed without any further checks, as explained below.
 
 The next three mandatory parameters are:
 
@@ -63,7 +68,8 @@ The full section is:
 .. code:: YAML
 
    GEARSink:
-     cut_off_radius:              1e-3           # Cut off radius of the sink particles (in internal units). This parameter should be adapted with the resolution.
+     use_fixed_cut_off_radius: 1                 # Are we using a fixed cutoff radius? If we are, in GEAR the cutoff radius is fixed at the value specified below, and the sink smoothing length is fixed at this value divided by kernel_gamma. If not, the cutoff radius varies with the sink smoothing length as r_cut = h*kernel_gamma.
+     cut_off_radius: 1e-3                        # Cut off radius of all the sinks in internal units. Ignored if use_fixed_cut_off_radius is 0. 
      f_acc: 0.1                                  # (Optional) Fraction of the cut_off_radius that determines if a gas particle should be swallowed wihtout additional check. It has to respect 0 <= f_acc <= 1. (Default: 0.1)
      temperature_threshold_K:        100         # Max temperature (in K) for forming a sink when density_threshold_Hpcm3 <= density <= maximal_density_threshold_Hpcm3.
      density_threshold_Hpcm3: 1e3                # Minimum gas density (in Hydrogen atoms/cm3) required to form a sink particle.
@@ -104,6 +110,8 @@ This problem can be mitigated by choosing a higher value of ``stellar_particle_m
 
 *If you do not want to change your parameters*, you can increase the ``sort_stack_size`` variable at the beginning ``runner_sort.c``. The default value is 10 in powers of 2 (so the stack size is 1024 particles). Increase it to the desired value. Be careful to not overestimate this.
 
+Note that if a cutoff radius is not specified, and the radius is instead left to vary with the local gas density, the smoothing length criterion is always satisfied.
+
 Guide to choose the the accretion radius or the density threshold
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
diff --git a/examples/GravityTests/MWPotential2014_circularorbit/params_unit_1.yml b/examples/GravityTests/MWPotential2014_circularorbit/params_unit_1.yml
index 9e7ff5e2bfebb0e19e7231d72bbf8efe3162278c..b7a481af39cf6debc45c780f0a5bc595d1593053 100755
--- a/examples/GravityTests/MWPotential2014_circularorbit/params_unit_1.yml
+++ b/examples/GravityTests/MWPotential2014_circularorbit/params_unit_1.yml
@@ -1,7 +1,7 @@
 # Define the system of units to use internally.
 InternalUnitSystem:
-  UnitMass_in_cgs:     1.98848e+43 # 10^10 Solar masses
-  UnitLength_in_cgs:   3.086e+21 # kpc
+  UnitMass_in_cgs:     1.988409870698051e+43 # 10^10 Solar masses
+  UnitLength_in_cgs:   3.0856775814913673e+21 # kpc
   UnitVelocity_in_cgs: 1e5       # 1 km / s in cm/s
   UnitCurrent_in_cgs:  1         # Amperes
   UnitTemp_in_cgs:     1         # Kelvin
@@ -46,7 +46,7 @@ MWPotential2014Potential:
   # Mdisk_Msun:       6.8e10
   # Rdisk_kpc:        3.0
   # Zdisk_kpc:        0.280
-  # amplitude:        1.0
+  # amplitude_Msun_per_kpc3: 1.0e10
   # r_1_kpc:          1.0
   # alpha:            1.8
   # r_c_kpc:          1.9
diff --git a/examples/GravityTests/MWPotential2014_circularorbit/params_unit_2.yml b/examples/GravityTests/MWPotential2014_circularorbit/params_unit_2.yml
index 8d6b232b11f301d20f1a41c4872b056cfe408c4f..f540c52575a88c7e70ae518c78086dd589665a7f 100755
--- a/examples/GravityTests/MWPotential2014_circularorbit/params_unit_2.yml
+++ b/examples/GravityTests/MWPotential2014_circularorbit/params_unit_2.yml
@@ -1,7 +1,7 @@
 # Define the system of units to use internally.
 InternalUnitSystem:
-  UnitMass_in_cgs:     1.98848e+43 # 10^10 Solar masses
-  UnitLength_in_cgs:   3.086e+24 # Mpc
+  UnitMass_in_cgs:     1.988409870698051e+33 # 10^10 Solar masses
+  UnitLength_in_cgs:   3.0856775814913673e+24 # Mpc
   UnitVelocity_in_cgs: 1e5       # 1 km / s in cm/s
   UnitCurrent_in_cgs:  1         # Amperes
   UnitTemp_in_cgs:     1         # Kelvin
@@ -45,7 +45,7 @@ MWPotential2014Potential:
   # Mdisk_Msun:       6.8e10
   # Rdisk_kpc:        3.0
   # Zdisk_kpc:        0.280
-  # amplitude:        1.0
+  # amplitude_Msun_per_kpc3: 1e10
   # r_1_kpc:          1.0
   # alpha:            1.8
   # r_c_kpc:          1.9
diff --git a/examples/IsolatedGalaxy/IsolatedGalaxy_multi_component/GEAR/params.yml b/examples/IsolatedGalaxy/IsolatedGalaxy_multi_component/GEAR/params.yml
index f12d35179c0ab5e7513ba41b8e15c624dfc700b2..e342a1ff8766370c130de070c4706bbf270c5910 100644
--- a/examples/IsolatedGalaxy/IsolatedGalaxy_multi_component/GEAR/params.yml
+++ b/examples/IsolatedGalaxy/IsolatedGalaxy_multi_component/GEAR/params.yml
@@ -1,59 +1,58 @@
-# Define the system of units to use internally. 
+# Define the system of units to use internally.
 InternalUnitSystem:
-  UnitMass_in_cgs:     1.98848e43    # 10^10 M_sun in grams
-  UnitLength_in_cgs:   3.08567758e21 # kpc in centimeters
-  UnitVelocity_in_cgs: 1e5           # km/s in centimeters per second
-  UnitCurrent_in_cgs:  1             # Amperes
-  UnitTemp_in_cgs:     1             # Kelvin
+  UnitMass_in_cgs: 1.98848e43 # 10^10 M_sun in grams
+  UnitLength_in_cgs: 3.08567758e21 # kpc in centimeters
+  UnitVelocity_in_cgs: 1e5 # km/s in centimeters per second
+  UnitCurrent_in_cgs: 1 # Amperes
+  UnitTemp_in_cgs: 1 # Kelvin
 
 Scheduler:
   max_top_level_cells: 16
-  cell_extra_gparts:   1000      # (Optional) Number of spare gparts per top-level allocated at rebuild time for on-the-fly creation.
-  cell_extra_sinks:    1000      # (Optional) Number of spare sinks per top-level allocated at rebuild time for on-the-fly creation.
-  cell_extra_sparts:   1000      # (Optional) Number of spare sparts per top-level allocated at rebuild time for on-the-fly creation.
+  cell_extra_gparts: 1000 # (Optional) Number of spare gparts per top-level allocated at rebuild time for on-the-fly creation.
+  cell_extra_sinks: 1000 # (Optional) Number of spare sinks per top-level allocated at rebuild time for on-the-fly creation.
+  cell_extra_sparts: 1000 # (Optional) Number of spare sparts per top-level allocated at rebuild time for on-the-fly creation.
 
-  
 # Parameters governing the time integration
 TimeIntegration:
-  time_begin: 0.    # The starting time of the simulation (in internal units).
-  time_end:   0.1   # The end time of the simulation (in internal units).
-  dt_min:     1e-10 # The minimal time-step size of the simulation (in internal units).
-  dt_max:     0.1  # The maximal time-step size of the simulation (in internal units).
-  max_dt_RMS_factor: 0.25  # (Optional) Dimensionless factor for the maximal displacement allowed based on the RMS velocities.
+  time_begin: 0. # The starting time of the simulation (in internal units).
+  time_end: 0.1 # The end time of the simulation (in internal units).
+  dt_min: 1e-10 # The minimal time-step size of the simulation (in internal units).
+  dt_max: 0.1 # The maximal time-step size of the simulation (in internal units).
+  max_dt_RMS_factor: 0.25 # (Optional) Dimensionless factor for the maximal displacement allowed based on the RMS velocities.
 
 # Parameters governing the snapshots
 Snapshots:
-  subdir:              snap  # snapshot directory
-  basename:            snapshot # Common part of the name of output files
-  time_first:          0.    # Time of the first output (in internal units)
-  delta_time:          1e-2  # Time difference between consecutive outputs (in internal units)
-  compression:         4
+  subdir: snap # snapshot directory
+  basename: snapshot # Common part of the name of output files
+  time_first: 0. # Time of the first output (in internal units)
+  delta_time: 1e-2 # Time difference between consecutive outputs (in internal units)
+  compression: 4
 
 # Parameters governing the conserved quantities statistics
 Statistics:
-  delta_time:          1e-3 # Time between statistics output
+  delta_time: 1e-3 # Time between statistics output
 
 # Parameters for the self-gravity scheme
 Gravity:
-  MAC:                           adaptive  # Choice of mulitpole acceptance criterion: 'adaptive' OR 'geometric'.
-  epsilon_fmm:                   0.001     # Tolerance parameter for the adaptive multipole acceptance criterion.
-  theta_cr:                      0.7       # Opening angle for the purely gemoetric criterion.
-  eta:          0.025               # Constant dimensionless multiplier for time integration.
-  max_physical_baryon_softening: 0.05  # Physical softening length (in internal units)
-  max_physical_DM_softening: 0.05  # Physical softening length (in internal units)
-  
+  MAC: adaptive # Choice of mulitpole acceptance criterion: 'adaptive' OR 'geometric'.
+  epsilon_fmm: 0.001 # Tolerance parameter for the adaptive multipole acceptance criterion.
+  theta_cr: 0.7 # Opening angle for the purely gemoetric criterion.
+  eta: 0.025 # Constant dimensionless multiplier for time integration.
+  max_physical_baryon_softening: 0.05 # Physical softening length (in internal units)
+  max_physical_DM_softening: 0.05 # Physical softening length (in internal units)
+
 # Parameters for the hydrodynamics scheme
-SPH:  
-  resolution_eta:        1.2348   # Target smoothing length in units of the mean inter-particle separation (1.2348 == 48Ngbs with the cubic spline kernel).
-  CFL_condition:         0.1      # Courant-Friedrich-Levy condition for time integration.
-  minimal_temperature:   10.      # Kelvin
-  h_max:                 10.      # (Optional) Maximal allowed smoothing length in internal units. Defaults to FLT_MAX if unspecified.
+SPH:
+  resolution_eta: 1.2348 # Target smoothing length in units of the mean inter-particle separation (1.2348 == 48Ngbs with the cubic spline kernel).
+  CFL_condition: 0.1 # Courant-Friedrich-Levy condition for time integration.
+  minimal_temperature: 10. # Kelvin
+  h_max: 10. # (Optional) Maximal allowed smoothing length in internal units. Defaults to FLT_MAX if unspecified.
 
 # Parameters related to the initial conditions
 InitialConditions:
-  file_name:  ./galaxy_multi_component.hdf5     # The file to read
-  periodic:   0                                 # Non-periodic BCs
-  shift:    [750,750,750]                       # Centre the box
+  file_name: ./galaxy_multi_component.hdf5 # The file to read
+  periodic: 0 # Non-periodic BCs
+  shift: [750, 750, 750] # Centre the box
 
 # Cooling with Grackle 2.0
 GrackleCooling:
@@ -64,63 +63,45 @@ GrackleCooling:
   provide_volumetric_heating_rates: 0 # User provide volumetric heating rates
   provide_specific_heating_rates: 0 # User provide specific heating rates
   self_shielding_method: -1 # Grackle (<= 3) or Gear self shielding method
-  self_shielding_threshold_atom_per_cm3: 0.007  # Required only with GEAR's self shielding. Density threshold of the self shielding
+  self_shielding_threshold_atom_per_cm3: 0.007 # Required only with GEAR's self shielding. Density threshold of the self shielding
   max_steps: 1000
   convergence_limit: 1e-2
   thermal_time_myr: 5
   maximal_density_Hpcm3: -1 # Maximal density (in hydrogen atoms/cm^3) for cooling. Higher densities are floored to this value to ensure grackle works properly when interpolating beyond the cloudy_table maximal density. A value < 0 deactivates this parameter.
 
 GEARStarFormation:
-  star_formation_efficiency: 0.01     # star formation efficiency (c_*)
-  maximal_temperature_K:     3e4      # Upper limit to the temperature of a star forming particle
-  density_threshold_Hpcm3:   0.1      # Density threshold in Hydrogen atoms/cm3
+  star_formation_efficiency: 0.01 # star formation efficiency (c_*)
+  maximal_temperature_K: 3e4 # Upper limit to the temperature of a star forming particle
+  density_threshold_Hpcm3: 0.1 # Density threshold in Hydrogen atoms/cm3
   n_stars_per_particle: 4
   min_mass_frac: 0.5
 
 GEARSink:
-  cut_off_radius: 5e-3                       # Cut off radius of all the sinks in internal units.
+  use_fixed_cut_off_radius: 1 # Are we using a fixed cutoff radius? If we are, in GEAR the cutoff radius is fixed at the value specified below, and the sink smoothing length is fixed at this value divided by kernel_gamma. If not, the cutoff radius varies with the sink smoothing length as r_cut = h*kernel_gamma.
+  cut_off_radius: 5e-3 # Cut off radius of all the sinks in internal units. Ignored if use_fixed_cut_off_radius is 0.
   f_acc: 0.1
-  temperature_threshold_K: 1e4               # Max temperature (in K) for forming a sink when density_threshold_Hpcm3 <= density <= maximal_density_threshold_Hpcm3.
-  density_threshold_Hpcm3: 1e0               # Minimum gas density (in Hydrogen atoms/cm3) required to form a sink particle.
-  maximal_density_threshold_Hpcm3: 1e5       # If the gas density exceeds this value (in Hydrogen atoms/cm3), a sink forms regardless of temperature if all other criteria are passed.
-  stellar_particle_mass_Msun: 1e5            # Mass of the stellar particle representing the low mass stars, in solar mass
-  minimal_discrete_mass_Msun: 30             # Minimal mass of stars represented by discrete particles, in solar mass
-  stellar_particle_mass_first_stars_Msun: 1e5 # Mass of the stellar particle representing the low mass stars, in solar mass
-  minimal_discrete_mass_first_stars_Msun: 30  # Minimal mass of stars represented by discrete particles, in solar mass
-  star_spawning_sigma_factor: 0.5             # Factor to rescale the velocity dispersion of the stars when they are spawned. (Default: 0.2)
-  sink_formation_contracting_gas_criterion: 1     # (Optional) Activate the contracting gas check for sink formation. (Default: 1)
-  sink_formation_smoothing_length_criterion: 0    # (Optional) Activate the smoothing length check for sink formation. (Default: 1)
-  sink_formation_jeans_instability_criterion: 1   # (Optional) Activate the two Jeans instability checks for sink formation. (Default: 1)
-  sink_formation_bound_state_criterion: 1         # (Optional) Activate the bound state check for sink formation. (Default: 1)
-  sink_formation_overlapping_sink_criterion: 1    # (Optional) Activate the overlapping sink check for sink formation. (Default: 1)
-  disable_sink_formation: 0                       # (Optional) Disable sink formation. (Default: 0)
-
-  # Timesteps parameters
-  CFL_condition:                        0.5       # Courant-Friedrich-Levy condition for time integration.
-  timestep_age_threshold_unlimited_Myr: 100.      # (Optional) Age above which sinks have no time-step restriction any more (in Mega-years). Defaults to 0.
-  timestep_age_threshold_Myr:           25.       # (Optional) Age at which sink switch from young to old for time-stepping purposes (in Mega-years). Defaults to FLT_MAX.
-  max_timestep_young_Myr:                1.       # (Optional) Maximal time-step length of young sinks (in Mega-years). Defaults to FLT_MAX.
-  max_timestep_old_Myr:                  5.       # (Optional) Maximal time-step length of old sinks (in Mega-years). Defaults to FLT_MAX.
-  n_IMF:                                 2        # (Optional) Number of times the IMF mass can be swallowed in a single timestep. (Default: FLTM_MAX)
-
-
-GEARSink:
-  cut_off_radius: 5e-3                       # Cut off radius of all the sinks in internal units.
-  f_acc: 0.1
-  maximal_temperature:  1e4                  # Upper limit to the temperature of a star forming particle
-  density_threshold_g_per_cm3:   1.67e-24    # Density threashold in g/cm3 (1.67e-24 =1acc)
-  stellar_particle_mass_Msun: 1e5            # Mass of the stellar particle representing the low mass stars, in solar mass
-  minimal_discrete_mass_Msun: 30             # Minimal mass of stars represented by discrete particles, in solar mass
+  temperature_threshold_K: 1e4 # Max temperature (in K) for forming a sink when density_threshold_Hpcm3 <= density <= maximal_density_threshold_Hpcm3.
+  density_threshold_Hpcm3: 1e0 # Minimum gas density (in Hydrogen atoms/cm3) required to form a sink particle.
+  maximal_density_threshold_Hpcm3: 1e5 # If the gas density exceeds this value (in Hydrogen atoms/cm3), a sink forms regardless of temperature if all other criteria are passed.
+  stellar_particle_mass_Msun: 1e5 # Mass of the stellar particle representing the low mass stars, in solar mass
+  minimal_discrete_mass_Msun: 30 # Minimal mass of stars represented by discrete particles, in solar mass
   stellar_particle_mass_first_stars_Msun: 1e5 # Mass of the stellar particle representing the low mass stars, in solar mass
   minimal_discrete_mass_first_stars_Msun: 30 # Minimal mass of stars represented by discrete particles, in solar mass
-  star_spawning_sigma_factor: 0.5             # Factor to rescale the velocity dispersion of the stars when they are spawned. (Default: 0.2)
-  sink_formation_contracting_gas_criterion: 1     # (Optional) Activate the contracting gas check for sink formation. (Default: 1)
-  sink_formation_smoothing_length_criterion: 0    # (Optional) Activate the smoothing length check for sink formation. (Default: 1)
-  sink_formation_jeans_instability_criterion: 1   # (Optional) Activate the two Jeans instability checks for sink formation. (Default: 1)
-  sink_formation_bound_state_criterion: 1         # (Optional) Activate the bound state check for sink formation. (Default: 1)
-  sink_formation_overlapping_sink_criterion: 1    # (Optional) Activate the overlapping sink check for sink formation. (Default: 1)
-  disable_sink_formation: 0                       # (Optional) Disable sink formation. (Default: 0)
+  star_spawning_sigma_factor: 0.5 # Factor to rescale the velocity dispersion of the stars when they are spawned. (Default: 0.2)
+  sink_formation_contracting_gas_criterion: 1 # (Optional) Activate the contracting gas check for sink formation. (Default: 1)
+  sink_formation_smoothing_length_criterion: 0 # (Optional) Activate the smoothing length check for sink formation. (Default: 1)
+  sink_formation_jeans_instability_criterion: 1 # (Optional) Activate the two Jeans instability checks for sink formation. (Default: 1)
+  sink_formation_bound_state_criterion: 1 # (Optional) Activate the bound state check for sink formation. (Default: 1)
+  sink_formation_overlapping_sink_criterion: 1 # (Optional) Activate the overlapping sink check for sink formation. (Default: 1)
+  disable_sink_formation: 0 # (Optional) Disable sink formation. (Default: 0)
 
+  # Timesteps parameters
+  CFL_condition: 0.5 # Courant-Friedrich-Levy condition for time integration.
+  timestep_age_threshold_unlimited_Myr: 100. # (Optional) Age above which sinks have no time-step restriction any more (in Mega-years). Defaults to 0.
+  timestep_age_threshold_Myr: 25. # (Optional) Age at which sink switch from young to old for time-stepping purposes (in Mega-years). Defaults to FLT_MAX.
+  max_timestep_young_Myr: 1. # (Optional) Maximal time-step length of young sinks (in Mega-years). Defaults to FLT_MAX.
+  max_timestep_old_Myr: 5. # (Optional) Maximal time-step length of old sinks (in Mega-years). Defaults to FLT_MAX.
+  n_IMF: 2 # (Optional) Number of times the IMF mass can be swallowed in a single timestep. (Default: FLTM_MAX)
 
 GEARPressureFloor:
   jeans_factor: 10
@@ -130,9 +111,9 @@ GEARFeedback:
   supernovae_efficiency: 0.1
   yields_table: POPIIsw.h5
   discrete_yields: 1
-  yields_table_first_stars: POPIIsw.h5          # Table containing the yields of the first stars.
-  metallicity_max_first_stars: -5                          # Maximal metallicity (in mass fraction) for a first star (-1 to deactivate).
-  elements: [Fe, Mg, O, C, Al, Ca, Ba, Zn, Eu]             # Elements to read in the yields table. The number of element should be one less than the number of elements (N) requested during the configuration (--with-chemistry=GEAR_N).
+  yields_table_first_stars: POPIIsw.h5 # Table containing the yields of the first stars.
+  metallicity_max_first_stars: -5 # Maximal metallicity (in mass fraction) for a first star (-1 to deactivate).
+  elements: [Fe, Mg, O, C, Al, Ca, Ba, Zn, Eu] # Elements to read in the yields table. The number of element should be one less than the number of elements (N) requested during the configuration (--with-chemistry=GEAR_N).
 
 GEARChemistry:
   initial_metallicity: 1
diff --git a/examples/IsolatedGalaxy/IsolatedGalaxy_sink/isolated_galaxy.yml b/examples/IsolatedGalaxy/IsolatedGalaxy_sink/isolated_galaxy.yml
index ce4671125474f8e6862056e65695676ec4418a09..c35f58e4a68c9b419c254d89c32092e92aacfac9 100644
--- a/examples/IsolatedGalaxy/IsolatedGalaxy_sink/isolated_galaxy.yml
+++ b/examples/IsolatedGalaxy/IsolatedGalaxy_sink/isolated_galaxy.yml
@@ -88,7 +88,8 @@ DefaultSink:
   cut_off_radius: 0.1      # Cut off radius of all the sinks in internal units.
 
 GEARSink:
-  cut_off_radius: 1e-2                       # Cut off radius of all the sinks in internal units.
+  use_fixed_cut_off_radius: 1                 # Are we using a fixed cutoff radius? If we are, in GEAR the cutoff radius is fixed at the value specified below, and the sink smoothing length is fixed at this value divided by kernel_gamma. If not, the cutoff radius varies with the sink smoothing length as r_cut = h*kernel_gamma.
+  cut_off_radius: 5e-3                        # Cut off radius of all the sinks in internal units. Ignored if use_fixed_cut_off_radius is 0. 
   f_acc: 1e-2
   temperature_threshold_K: 5e3               # Max temperature (in K) for forming a sink when density_threshold_Hpcm3 <= density <= maximal_density_threshold_Hpcm3.
   density_threshold_Hpcm3: 1e0               # Minimum gas density (in Hydrogen atoms/cm3) required to form a sink particle.
diff --git a/examples/SinkParticles/HomogeneousBox/params.yml b/examples/SinkParticles/HomogeneousBox/params.yml
index a805594d057141a4ed49c443f62b70a12f09a9b6..ac851c5e3ed4236887fc95741e9985cdc3817ce7 100755
--- a/examples/SinkParticles/HomogeneousBox/params.yml
+++ b/examples/SinkParticles/HomogeneousBox/params.yml
@@ -85,7 +85,8 @@ GEARFeedback:
 
 
 GEARSink:
-  cut_off_radius: 5e-3                        # Cut off radius of all the sinks in internal units.
+  use_fixed_cut_off_radius: 1                 # Are we using a fixed cutoff radius? If we are, in GEAR the cutoff radius is fixed at the value specified below, and the sink smoothing length is fixed at this value divided by kernel_gamma. If not, the cutoff radius varies with the sink smoothing length as r_cut = h*kernel_gamma.
+  cut_off_radius: 5e-3                        # Cut off radius of all the sinks in internal units. Ignored if use_fixed_cut_off_radius is 0. 
   f_acc: 1e-2
   temperature_threshold_K: 1000               # Max temperature (in K) for forming a sink when density_threshold_Hpcm3 <= density <= maximal_density_threshold_Hpcm3.
   density_threshold_Hpcm3: 1e1                # Minimum gas density (in g/cm3) required to form a sink particle.
diff --git a/examples/SinkParticles/HomogeneousBoxSinkParticles/params.yml b/examples/SinkParticles/HomogeneousBoxSinkParticles/params.yml
index 90bd18a419bad00d3191df710d337c351b058d6a..2b3f15b939a328b263e8f507980fd38d4bfbe025 100755
--- a/examples/SinkParticles/HomogeneousBoxSinkParticles/params.yml
+++ b/examples/SinkParticles/HomogeneousBoxSinkParticles/params.yml
@@ -1,104 +1,104 @@
 # Define the system of units to use internally.
 InternalUnitSystem:
-  UnitMass_in_cgs:     1.988409870698051e+43   # 10^10 solar masses 
-  UnitLength_in_cgs:   3.0856775814913673e+21  # 1 kpc 
-  UnitVelocity_in_cgs: 1e5   # km/s
-  UnitCurrent_in_cgs:  1   # Amperes
-  UnitTemp_in_cgs:     1   # Kelvin
+  UnitMass_in_cgs: 1.988409870698051e+43 # 10^10 solar masses
+  UnitLength_in_cgs: 3.0856775814913673e+21 # 1 kpc
+  UnitVelocity_in_cgs: 1e5 # km/s
+  UnitCurrent_in_cgs: 1 # Amperes
+  UnitTemp_in_cgs: 1 # Kelvin
 
 # Parameters for the self-gravity scheme
 Gravity:
-  MAC:                           adaptive  # Choice of mulitpole acceptance criterion: 'adaptive' OR 'geometric'.
-  epsilon_fmm:                   0.001     # Tolerance parameter for the adaptive multipole acceptance criterion.
-  theta_cr:                      0.7       # Opening angle for the purely gemoetric criterion.
-  eta:          0.025               # Constant dimensionless multiplier for time integration.
-  theta:        0.7                 # Opening angle (Multipole acceptance criterion).
-  max_physical_baryon_softening: 0.005  # Physical softening length (in internal units)
-  mesh_side_length:        32
+  MAC: adaptive # Choice of mulitpole acceptance criterion: 'adaptive' OR 'geometric'.
+  epsilon_fmm: 0.001 # Tolerance parameter for the adaptive multipole acceptance criterion.
+  theta_cr: 0.7 # Opening angle for the purely gemoetric criterion.
+  eta: 0.025 # Constant dimensionless multiplier for time integration.
+  theta: 0.7 # Opening angle (Multipole acceptance criterion).
+  max_physical_baryon_softening: 0.005 # Physical softening length (in internal units)
+  mesh_side_length: 32
 
 # Parameters governing the time integration (Set dt_min and dt_max to the same value for a fixed time-step run.)
 TimeIntegration:
-  time_begin:        0.    # The starting time of the simulation (in internal units).
-  time_end:          0.282 # The end time of the simulation (in internal units).
-  dt_min:            1e-10 # The minimal time-step size of the simulation (in internal units).
-  dt_max:            2e-2  # The maximal time-step size of the simulation (in internal units).
+  time_begin: 0. # The starting time of the simulation (in internal units).
+  time_end: 0.282 # The end time of the simulation (in internal units).
+  dt_min: 1e-10 # The minimal time-step size of the simulation (in internal units).
+  dt_max: 2e-2 # The maximal time-step size of the simulation (in internal units).
 
 # Parameters governing the snapshots
 Snapshots:
   subdir: snap
-  basename:   snapshot      # Common part of the name of output files
+  basename: snapshot # Common part of the name of output files
   time_first: 0. #230 Myr # (Optional) Time of the first output if non-cosmological time-integration (in internal units)
-  delta_time: 10e-3        # Time difference between consecutive outputs (in internal units)
+  delta_time: 10e-3 # Time difference between consecutive outputs (in internal units)
 
 Scheduler:
-  cell_extra_gparts: 100      # (Optional) Number of spare sparts per top-level allocated at rebuild time for on-the-fly creation.
-  cell_extra_sinks: 100       # (Optional) Number of spare sparts per top-level allocated at rebuild time for on-the-fly creation.
-  cell_extra_sparts: 100      # (Optional) Number of spare sparts per top-level allocated at rebuild time for on-the-fly creation.
-  max_top_level_cells: 3        #
+  cell_extra_gparts: 100 # (Optional) Number of spare sparts per top-level allocated at rebuild time for on-the-fly creation.
+  cell_extra_sinks: 100 # (Optional) Number of spare sparts per top-level allocated at rebuild time for on-the-fly creation.
+  cell_extra_sparts: 100 # (Optional) Number of spare sparts per top-level allocated at rebuild time for on-the-fly creation.
+  max_top_level_cells: 3 #
 
 # Parameters governing the conserved quantities statistics
 Statistics:
-  delta_time:           5e-3     # Time between statistics output
-  time_first:             0.     # (Optional) Time of the first stats output if non-cosmological time-integration (in internal units)
+  delta_time: 5e-3 # Time between statistics output
+  time_first: 0. # (Optional) Time of the first stats output if non-cosmological time-integration (in internal units)
 
 # Parameters related to the initial conditions
 InitialConditions:
-  file_name:          ICs_homogeneous_box.hdf5
-  periodic:                    1    # Are we running with periodic ICs?
-  shift:              [0.0,0.0,0.0]
+  file_name: ICs_homogeneous_box.hdf5
+  periodic: 1 # Are we running with periodic ICs?
+  shift: [0.0, 0.0, 0.0]
 
 # Parameters for the hydrodynamics scheme
 SPH:
-  resolution_eta:        1.2348   # Target smoothing length in units of the mean inter-particle separation (1.2348 == 57Ngbs with the Wendland C2 kernel).
-  CFL_condition:         0.1      # Courant-Friedrich-Levy condition for time integration.
-  h_max:                 5
-  minimal_temperature:   1
-
+  resolution_eta: 1.2348 # Target smoothing length in units of the mean inter-particle separation (1.2348 == 57Ngbs with the Wendland C2 kernel).
+  CFL_condition: 0.1 # Courant-Friedrich-Levy condition for time integration.
+  h_max: 5
+  minimal_temperature: 1
 
 # Cooling with Grackle 3.0
 GrackleCooling:
-  cloudy_table: CloudyData_UVB=HM2012.h5        # Name of the Cloudy Table (available on the grackle bitbucket repository)
-  with_UV_background: 0                         # Enable or not the UV background
-  redshift: -1                                  # Redshift to use (-1 means time based redshift)
-  with_metal_cooling: 1                         # Enable or not the metal cooling
-  provide_volumetric_heating_rates: 0           # User provide volumetric heating rates
-  provide_specific_heating_rates: 0             # User provide specific heating rates
-  self_shielding_method: -1                     # Grackle (<= 3) or Gear self shielding method
-  self_shielding_threshold_atom_per_cm3: 0.007  # Required only with GEAR's self shielding. Density threshold of the self shielding
+  cloudy_table: CloudyData_UVB=HM2012.h5 # Name of the Cloudy Table (available on the grackle bitbucket repository)
+  with_UV_background: 0 # Enable or not the UV background
+  redshift: -1 # Redshift to use (-1 means time based redshift)
+  with_metal_cooling: 1 # Enable or not the metal cooling
+  provide_volumetric_heating_rates: 0 # User provide volumetric heating rates
+  provide_specific_heating_rates: 0 # User provide specific heating rates
+  self_shielding_method: -1 # Grackle (<= 3) or Gear self shielding method
+  self_shielding_threshold_atom_per_cm3: 0.007 # Required only with GEAR's self shielding. Density threshold of the self shielding
   max_steps: 1000
   convergence_limit: 1e-2
   thermal_time_myr: 5
-  maximal_density_Hpcm3: -1                     # Maximal density (in hydrogen atoms/cm^3) for cooling. Higher densities are floored to this value to ensure grackle works properly when interpolating beyond the cloudy_table maximal density. A value < 0 deactivates this parameter.
+  maximal_density_Hpcm3: -1 # Maximal density (in hydrogen atoms/cm^3) for cooling. Higher densities are floored to this value to ensure grackle works properly when interpolating beyond the cloudy_table maximal density. A value < 0 deactivates this parameter.
 
 GEARChemistry:
   initial_metallicity: 0
 
 GEARFeedback:
-  supernovae_energy_erg: 1e51                     # supernovae energy, used only for SNIa
-  supernovae_efficiency: 0.1                      # supernovae energy efficiency, used for both SNIa and SNII
+  supernovae_energy_erg: 1e51 # supernovae energy, used only for SNIa
+  supernovae_efficiency: 0.1 # supernovae energy efficiency, used for both SNIa and SNII
   yields_table: POPIIsw.h5
   yields_table_first_stars: POPIIsw.h5
   discrete_yields: 1
-  imf_transition_metallicity: -5                  # Maximal metallicity ([Fe/H]) for a first star (0 to deactivate).
-  elements: [Fe, Mg, O, C, Al, Ca, Ba, Zn, Eu]    # Elements to read in the yields table. The number of element should be one less than the number of elements (N) requested during the configuration (--with-chemistry=GEAR_N).
+  imf_transition_metallicity: -5 # Maximal metallicity ([Fe/H]) for a first star (0 to deactivate).
+  elements: [Fe, Mg, O, C, Al, Ca, Ba, Zn, Eu] # Elements to read in the yields table. The number of element should be one less than the number of elements (N) requested during the configuration (--with-chemistry=GEAR_N).
 
 GEARSink:
-  cut_off_radius: 5e-3                            # Cut off radius of all the sinks in internal units.
+  use_fixed_cut_off_radius: 1 # Are we using a fixed cutoff radius? If we are, in GEAR the cutoff radius is fixed at the value specified below, and the sink smoothing length is fixed at this value divided by kernel_gamma. If not, the cutoff radius varies with the sink smoothing length as r_cut = h*kernel_gamma.
+  cut_off_radius: 5e-3 # Cut off radius of all the sinks in internal units. Ignored if use_fixed_cut_off_radius is 0.
   f_acc: 0.1
-  temperature_threshold_K:       100              # Max temperature (in K) for forming a sink when density_threshold_g_per_cm3 <= density <= maximal_density_threshold_g_per_cm3.
-  density_threshold_Hpcm3: 1e1           # Minimum gas density (in g/cm3) required to form a sink particle.
-  maximal_density_threshold_Hpcm3: 1e5   # If the gas density exceeds this value (in g/cm3), a sink forms regardless of temperature if all other criteria are passed.
-  stellar_particle_mass_Msun: 50                  # Mass of the stellar particle representing the low mass stars, in solar mass
-  minimal_discrete_mass_Msun: 8                   # Minimal mass of stars represented by discrete particles, in solar mass
-  stellar_particle_mass_first_stars_Msun: 50      # Mass of the stellar particle representing the low mass stars, in solar mass
-  minimal_discrete_mass_first_stars_Msun: 8       # Minimal mass of stars represented by discrete particles, in solar mass
-  star_spawning_sigma_factor: 0.5                 # Factor to rescale the velocity dispersion of the stars when they are spawned. (Default: 0.2)
-  sink_formation_contracting_gas_criterion:   1   # (Optional) Activate the contracting gas check for sink formation. (Default: 1)
-  sink_formation_smoothing_length_criterion:  1   # (Optional) Activate the smoothing length check for sink formation. (Default: 1)
-  sink_formation_jeans_instability_criterion: 1   # (Optional) Activate the two Jeans instability checks for sink formation. (Default: 1)
-  sink_formation_bound_state_criterion:       1   # (Optional) Activate the bound state check for sink formation. (Default: 1)
-  sink_formation_overlapping_sink_criterion:  1   # (Optional) Activate the overlapping sink check for sink formation. (Default: 1)
-  disable_sink_formation:                     0   # (Optional) Disable sink formation. (Default: 0)
+  temperature_threshold_K: 100 # Max temperature (in K) for forming a sink when density_threshold_g_per_cm3 <= density <= maximal_density_threshold_g_per_cm3.
+  density_threshold_Hpcm3: 1e1 # Minimum gas density (in g/cm3) required to form a sink particle.
+  maximal_density_threshold_Hpcm3: 1e5 # If the gas density exceeds this value (in g/cm3), a sink forms regardless of temperature if all other criteria are passed.
+  stellar_particle_mass_Msun: 50 # Mass of the stellar particle representing the low mass stars, in solar mass
+  minimal_discrete_mass_Msun: 8 # Minimal mass of stars represented by discrete particles, in solar mass
+  stellar_particle_mass_first_stars_Msun: 50 # Mass of the stellar particle representing the low mass stars, in solar mass
+  minimal_discrete_mass_first_stars_Msun: 8 # Minimal mass of stars represented by discrete particles, in solar mass
+  star_spawning_sigma_factor: 0.5 # Factor to rescale the velocity dispersion of the stars when they are spawned. (Default: 0.2)
+  sink_formation_contracting_gas_criterion: 1 # (Optional) Activate the contracting gas check for sink formation. (Default: 1)
+  sink_formation_smoothing_length_criterion: 1 # (Optional) Activate the smoothing length check for sink formation. (Default: 1)
+  sink_formation_jeans_instability_criterion: 1 # (Optional) Activate the two Jeans instability checks for sink formation. (Default: 1)
+  sink_formation_bound_state_criterion: 1 # (Optional) Activate the bound state check for sink formation. (Default: 1)
+  sink_formation_overlapping_sink_criterion: 1 # (Optional) Activate the overlapping sink check for sink formation. (Default: 1)
+  disable_sink_formation: 0 # (Optional) Disable sink formation. (Default: 0)
 
   # Timesteps parameters
-  CFL_condition:                        0.5       # Courant-Friedrich-Levy condition for time integration.
+  CFL_condition: 0.5 # Courant-Friedrich-Levy condition for time integration.
diff --git a/examples/SinkParticles/PlummerSphere/params.yml b/examples/SinkParticles/PlummerSphere/params.yml
index 436773e49e99d9f51caad8e8549e884e7e15b1d9..262c7dba9742e9d92e35b1e3525aeacfbd5cec65 100755
--- a/examples/SinkParticles/PlummerSphere/params.yml
+++ b/examples/SinkParticles/PlummerSphere/params.yml
@@ -84,7 +84,8 @@ GEARFeedback:
   elements: [Fe, Mg, O, C, Al, Ca, Ba, Zn, Eu]              # Elements to read in the yields table. The number of element should be one less than the number of elements (N) requested during the configuration (--with-chemistry=GEAR_N).
 
 GEARSink:
-  cut_off_radius: 1e-2                       # Cut off radius of all the sinks in internal units.
+  use_fixed_cut_off_radius: 1                 # Are we using a fixed cutoff radius? If we are, in GEAR the cutoff radius is fixed at the value specified below, and the sink smoothing length is fixed at this value divided by kernel_gamma. If not, the cutoff radius varies with the sink smoothing length as r_cut = h*kernel_gamma.
+  cut_off_radius: 1e-2                        # Cut off radius of all the sinks in internal units. Ignored if use_fixed_cut_off_radius is 0. 
   f_acc: 0.4
   temperature_threshold_K: 3e4               # Max temperature (in K) for forming a sink when density_threshold_Hpcm3 <= density <= maximal_density_threshold_Hpcm3.
   density_threshold_Hpcm3: 1e0               # Minimum gas density (in Hydrogen atoms/cm3) required to form a sink particle.
diff --git a/examples/SinkParticles/SingleSink/README b/examples/SinkParticles/SingleSink/README
new file mode 100644
index 0000000000000000000000000000000000000000..1498298fce434d0729a5ba32ed72d037cf0f570c
--- /dev/null
+++ b/examples/SinkParticles/SingleSink/README
@@ -0,0 +1,31 @@
+# Intro
+This example is a non cosmological homogeneous box containing gas and a single sink in the centre. It's designed for testing that your sink model accretes the amount of gas that you're expecting. It's mainly designed to test the Basic model, which requires no extra subgrid physics to function.
+
+# ICs
+The included python script `make_sink_ic.py` creates the initial conditions. You can set the particle masses of the gas and sink, the gas density and velocity dispersion (which sets the internal energy), and set an overall "level", which determines how many particles to simulate (N_particle = 2^(3*level)). Run `python make_sink_ic.py --help` to get the list of parameters.
+
+Running the script with no options produces a "level 6" box at "m5" resolution, with a sink mass 50x that of the gas mass, and a density and velocity dispersion of 0.1 atoms/cc and 10 km/s respectively, representative of the ISM. The ICs have the default name `ics.hdf5`.
+
+# Configure
+To run this example with GEAR model,
+
+./configure --with-sink=Basic --with-kernel=wendland-C2
+
+and then
+
+make -j
+
+You can also add `--enable-sink-density-checks=<CHECK_FREQUENCY>` to run the brute-force density checks for the sink, to make sure things are being calculated properly.
+
+# Run
+We can now run with 
+
+swift --hydro --self-gravity --sinks --threads=<NUM_THREADS> params.yml
+
+By default the simulation will run for 500 Myr, and output a snapshot every 10 Myr. You can change these options in your paramfile.
+
+# Testing the output
+
+Included in this directory is a very simple script for making some test plots. You can run it with
+
+python make_test_plots.py <snapshot-directory-name>
\ No newline at end of file
diff --git a/examples/SinkParticles/SingleSink/make_sink_ic.py b/examples/SinkParticles/SingleSink/make_sink_ic.py
new file mode 100755
index 0000000000000000000000000000000000000000..12179ede3259e0c65089d22844b07611548289cc
--- /dev/null
+++ b/examples/SinkParticles/SingleSink/make_sink_ic.py
@@ -0,0 +1,236 @@
+################################################################################
+# This file is part of SWIFT.
+# Copyright (c) 2022 Yves Revaz (yves.revaz@epfl.ch)
+# Copyright (c) 2024 Jonathan Davies (j.j.davies@ljmu.ac.uk)
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published
+# by the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+################################################################################
+
+import h5py
+import numpy as np
+from optparse import OptionParser
+from astropy import units
+from astropy import constants
+
+
+def parse_options():
+
+    usage = "usage: %prog [options] file"
+    parser = OptionParser(usage=usage)
+
+    parser.add_option(
+        "--rho",
+        action="store",
+        dest="rho",
+        type="float",
+        default=0.1,
+        help="Mean gas density in atom/cm3",
+    )
+
+    parser.add_option(
+        "--sigma",
+        action="store",
+        dest="sigma",
+        type="float",
+        default=10.0,
+        help="Velocity dispersion of the gas in km/s (sets internal energy).",
+    )
+
+    parser.add_option(
+        "--gas-mass",
+        action="store",
+        dest="gas_mass",
+        type="float",
+        default=1e5,
+        help="Gas particle mass in solar masses",
+    )
+
+    parser.add_option(
+        "--sink-mass",
+        action="store",
+        dest="sink_mass",
+        type="float",
+        default=5e6,
+        help="Sink particle mass in solar masses",
+    )
+
+    parser.add_option(
+        "--sink-velocity",
+        action="store",
+        dest="sink_mach",
+        type="float",
+        default=0.0,
+        help="Sink velocity as a multiple of the sound speed (i.e. Mach number)",
+    )
+
+    parser.add_option(
+        "--level",
+        action="store",
+        dest="level",
+        type="int",
+        default=6,
+        help="Resolution level: N = (2**l)**3",
+    )
+
+    parser.add_option(
+        "-o",
+        action="store",
+        dest="outputfilename",
+        type="string",
+        default="ics.hdf5",
+        help="output filename",
+    )
+
+    (options, args) = parser.parse_args()
+
+    files = args
+
+    return files, options
+
+
+########################################
+# main
+########################################
+
+files, opt = parse_options()
+
+# define standard units
+UnitMass_in_cgs = 1.988409870698051e43  # 10^10 M_sun in grams
+UnitLength_in_cgs = 3.0856775814913673e21  # kpc in centimeters
+UnitVelocity_in_cgs = 1e5  # km/s in centimeters per second
+UnitCurrent_in_cgs = 1  # Amperes
+UnitTemp_in_cgs = 1  # Kelvin
+UnitTime_in_cgs = UnitLength_in_cgs / UnitVelocity_in_cgs
+
+UnitMass = UnitMass_in_cgs * units.g
+UnitLength = UnitLength_in_cgs * units.cm
+UnitTime = UnitTime_in_cgs * units.s
+UnitVelocity = UnitVelocity_in_cgs * units.cm / units.s
+
+np.random.seed(1)
+
+# Number of particles
+N = (2 ** opt.level) ** 3  # number of particles
+
+# Mean density
+rho = opt.rho  # atom/cc
+rho = rho * constants.m_p / units.cm ** 3
+
+# Gas particle mass
+m = opt.gas_mass * units.Msun  # in solar mass
+
+# Sink particle mass
+sm = opt.sink_mass * units.Msun  # in solar mass
+
+# Gas mass in the box
+M = N * m
+
+# Size of the box
+L = (M / rho) ** (1 / 3.0)
+
+# Gravitational constant
+G = constants.G
+
+print("Number of particles                   : {}".format(N))
+
+# Convert to code units
+m = m.to(UnitMass).value
+sm = sm.to(UnitMass).value
+L = L.to(UnitLength).value
+rho = rho.to(UnitMass / UnitLength ** 3).value
+sigma = (opt.sigma * units.km / units.s).to(UnitVelocity).value
+
+# Generate the particles
+pos = np.random.random([N, 3]) * np.array([L, L, L])
+vel = np.zeros([N, 3])
+mass = np.ones(N) * m
+u = np.ones(N) * sigma ** 2
+ids = np.arange(N)
+h = np.ones(N) * 3 * L / N ** (1 / 3.0)
+rho = np.ones(N) * rho
+
+print("Inter-particle distance (code unit)   : {}".format(L / N ** (1 / 3.0)))
+
+# Generate the sink
+# Always put it 10% of the way through the box to give it room to move (if it's going to)
+
+NSINK = 1
+
+sink_pos = np.ones([NSINK, 3])
+sink_pos[:, 0] = L / 10.0
+sink_pos[:, 1] = L / 2.0
+sink_pos[:, 2] = L / 2.0
+
+sink_mass = np.array([sm])
+sink_ids = np.array([2 * ids[-1]])
+sink_h = np.array([3 * L / N ** (1 / 3.0)])
+
+gas_cs = np.sqrt(sigma ** 2 * 5.0 / 3.0 * ((5.0 / 3.0) - 1))
+
+sink_vel = np.zeros([NSINK, 3])
+sink_vel[:, 0] += gas_cs * opt.sink_mach
+
+print(f"Sink velocity: {gas_cs * opt.sink_mach}")
+
+if gas_cs * opt.sink_mach > 0:
+    sink_time_in_box = L * 0.9 / (gas_cs * opt.sink_mach)
+    print(
+        f"Sink will leave box (neglecting dynamical friction) after time: {sink_time_in_box}"
+    )
+
+
+# File
+fileOutput = h5py.File(opt.outputfilename, "w")
+
+# Header
+grp = fileOutput.create_group("/Header")
+grp.attrs["BoxSize"] = [L, L, L]
+grp.attrs["NumPart_Total"] = [N, 0, 0, NSINK, 0, 0]
+grp.attrs["NumPart_Total_HighWord"] = [0, 0, 0, 0, 0, 0]
+grp.attrs["NumPart_ThisFile"] = [N, 0, 0, NSINK, 0, 0]
+grp.attrs["Time"] = 0.0
+grp.attrs["NumFileOutputsPerSnapshot"] = 1
+grp.attrs["MassTable"] = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
+grp.attrs["Flag_Entropy_ICs"] = [0, 0, 0, 0, 0, 0]
+grp.attrs["Dimension"] = 3
+
+# Units
+grp = fileOutput.create_group("/Units")
+grp.attrs["Unit length in cgs (U_L)"] = UnitLength_in_cgs
+grp.attrs["Unit mass in cgs (U_M)"] = UnitMass_in_cgs
+grp.attrs["Unit time in cgs (U_t)"] = UnitTime_in_cgs
+grp.attrs["Unit current in cgs (U_I)"] = UnitCurrent_in_cgs
+grp.attrs["Unit temperature in cgs (U_T)"] = UnitTemp_in_cgs
+
+# Particle groups
+grp = fileOutput.create_group("/PartType0")
+grp.create_dataset("Coordinates", data=pos, dtype="d")
+grp.create_dataset("Velocities", data=vel, dtype="f")
+grp.create_dataset("Masses", data=mass, dtype="f")
+grp.create_dataset("SmoothingLength", data=h, dtype="f")
+grp.create_dataset("InternalEnergy", data=u, dtype="f")
+grp.create_dataset("ParticleIDs", data=ids, dtype="L")
+grp.create_dataset("Densities", data=rho, dtype="f")
+
+grp = fileOutput.create_group("/PartType3")
+grp.create_dataset("Coordinates", data=sink_pos, dtype="d")
+grp.create_dataset("Velocities", data=sink_vel, dtype="f")
+grp.create_dataset("Masses", data=sink_mass, dtype="f")
+grp.create_dataset("SmoothingLength", data=sink_h, dtype="f")
+grp.create_dataset("ParticleIDs", data=sink_ids, dtype="L")
+
+fileOutput.close()
+
+print(f"{opt.outputfilename} saved.")
diff --git a/examples/SinkParticles/SingleSink/make_test_plots.py b/examples/SinkParticles/SingleSink/make_test_plots.py
new file mode 100644
index 0000000000000000000000000000000000000000..7bb69048cbcaed90d974ddb29cb2db4cc618c4d7
--- /dev/null
+++ b/examples/SinkParticles/SingleSink/make_test_plots.py
@@ -0,0 +1,135 @@
+################################################################################
+# This file is part of SWIFT.
+# Copyright (c) 2024 Jonathan Davies (j.j.davies@ljmu.ac.uk)
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published
+# by the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+################################################################################
+
+"""
+Make some very simple plots showing the evolution of the single sink.
+
+Run with python make_test_plots.py <snapshot-directory-name>
+
+"""
+
+
+import numpy as np
+from sys import argv
+from glob import glob
+import matplotlib.pyplot as plt
+import unyt
+import h5py
+
+params = {
+    "text.usetex": True,
+    "axes.labelsize": 16,
+    "xtick.labelsize": 13,
+    "ytick.labelsize": 13,
+    "lines.linewidth": 2,
+    "axes.titlesize": 16,
+    "font.family": "serif",
+}
+plt.rcParams.update(params)
+
+# Units
+G_cgs = 6.6743e-8 * unyt.cm ** 3 * unyt.g ** -1 * unyt.s ** -2
+unit_mass_cgs = 1.988409870698051e43 * unyt.g
+unit_density_cgs = 6.767905323247329e-22 * unyt.Unit("g/cm**3")
+unit_velocity_cgs = (1.0 * unyt.Unit("km/s")).to("cm/s")
+
+
+# Basic Bondi-Hoyle prediction from the sink's starting mass in a constant medium
+def simple_bondi_hoyle(t, m, rho, v, cs):
+
+    m_sink = np.zeros(len(t), dtype=np.float64) * unyt.g
+
+    m_sink[0] = m[0] * unit_mass_cgs
+
+    rho_0 = rho[0] * unit_density_cgs
+    v_0 = v[0] * unit_velocity_cgs
+    cs_0 = cs[0] * unit_velocity_cgs
+
+    timestep = ((t[1] - t[0]) * unyt.Gyr).to("s")
+
+    for i in range(len(times) - 1):
+
+        numerator = 4.0 * np.pi * G_cgs ** 2 * m_sink[i] ** 2 * rho_0
+        denominator = np.power(v_0 ** 2 + cs_0 ** 2, 3.0 / 2.0)
+
+        m_sink[i + 1] = m_sink[i] + (numerator / denominator) * timestep
+
+    return m_sink.to("Msun")
+
+
+snapshots = glob(f"./{argv[1]}/*.hdf5")
+
+times = np.empty(len(snapshots), dtype=np.float32)
+mass_evo = np.empty(len(snapshots), dtype=np.float32)
+subgrid_mass_evo = np.empty(len(snapshots), dtype=np.float32)
+rho_evo = np.empty(len(snapshots), dtype=np.float32)
+v_evo = np.empty(len(snapshots), dtype=np.float32)
+cs_evo = np.empty(len(snapshots), dtype=np.float32)
+hsml_evo = np.empty(len(snapshots), dtype=np.float32)
+
+
+for s, snap in enumerate(snapshots):
+
+    with h5py.File(snap) as f:
+        times[s] = f["Header"].attrs["Time"][0]
+        mass_evo[s] = f["PartType3/Masses"][0]
+        subgrid_mass_evo[s] = f["PartType3/SubgridMasses"][0]
+        rho_evo[s] = f["PartType3/GasDensities"][0]
+        cs_evo[s] = f["PartType3/GasSoundSpeeds"][0]
+        hsml_evo[s] = f["PartType3/SmoothingLengths"][0]
+
+        v = f["PartType3/GasVelocities"][0] - f["PartType3/Velocities"][0]
+        v_evo[s] = np.sqrt(v[0] ** 2 + v[1] ** 2 + v[2] ** 2)
+
+
+# Run the Bondi-Hoyle prediction
+m_sink_bondi_prediction = simple_bondi_hoyle(times, mass_evo, rho_evo, v_evo, cs_evo)
+
+# Normalise time to go up to 1
+t = times / times[-1]
+
+# Time evolution of the sink mass, target mass, and Bondi-Hoyle prediction
+fig, ax = plt.subplots(1, figsize=(8, 6))
+ax.plot(
+    t,
+    np.log10(m_sink_bondi_prediction),
+    label=r"Simple Bondi-Hoyle, constant $\rho$, $v$, $c_{\rm s}$",
+)
+ax.plot(t, np.log10(subgrid_mass_evo * 1e10), label=r"Subgrid mass")
+ax.plot(t, np.log10(mass_evo * 1e10), label=r"Dynamical mass")
+
+ax.set_xlabel(r"$t/t_{\rm final}$")
+ax.set_ylabel(r"$\log_{10}(M_{\rm sink})$")
+ax.legend(fontsize=14)
+ax.set_ylim(6.68, 7.0)
+fig.savefig("mass_evolution.png", bbox_inches="tight")
+
+# Time evolution of the total mass accreted relative to the target mass
+fig, ax = plt.subplots(1, figsize=(8, 6))
+ax.plot(t, mass_evo / subgrid_mass_evo)
+ax.set_xlabel(r"$t/t_{\rm final}$")
+ax.set_ylabel(r"$m_{\rm accr}^{\rm gas}/m_{\rm target}^{\rm gas}$")
+fig.savefig("target_mass_ratio.png", bbox_inches="tight")
+
+# Time evolution of the smoothing length
+fig, ax = plt.subplots(1, figsize=(8, 6))
+ax.plot(t, hsml_evo)
+ax.set_xlabel(r"$t/t_{\rm final}$")
+ax.set_ylabel(r"$h_{\rm sink}$ [kpc]")
+fig.savefig("smoothing_length.png", bbox_inches="tight")
diff --git a/examples/SinkParticles/SingleSink/params.yml b/examples/SinkParticles/SingleSink/params.yml
new file mode 100755
index 0000000000000000000000000000000000000000..eafa709d3687fd55ff2f2c73a5f8176c58c94b38
--- /dev/null
+++ b/examples/SinkParticles/SingleSink/params.yml
@@ -0,0 +1,61 @@
+# Define the system of units to use internally.
+InternalUnitSystem:
+  UnitMass_in_cgs:     1.988409870698051e+43   # 10^10 solar masses 
+  UnitLength_in_cgs:   3.0856775814913673e+21  # 1 kpc 
+  UnitVelocity_in_cgs: 1e5   # km/s
+  UnitCurrent_in_cgs:  1   # Amperes
+  UnitTemp_in_cgs:     1   # Kelvin
+
+# Parameters for the self-gravity scheme
+Gravity:
+  MAC:                           adaptive  # Choice of mulitpole acceptance criterion: 'adaptive' OR 'geometric'.
+  epsilon_fmm:                   0.001     # Tolerance parameter for the adaptive multipole acceptance criterion.
+  theta_cr:                      0.7       # Opening angle for the purely gemoetric criterion.
+  eta:          0.025               # Constant dimensionless multiplier for time integration.
+  theta:        0.7                 # Opening angle (Multipole acceptance criterion).
+  max_physical_baryon_softening: 0.35  # Physical softening length (in internal units)
+  mesh_side_length:        32
+
+# Parameters governing the time integration (Set dt_min and dt_max to the same value for a fixed time-step run.)
+TimeIntegration:
+  time_begin:        0.    # The starting time of the simulation (in internal units).
+  time_end:          0.5   # 500 Myr # The end time of the simulation (in internal units).
+  dt_min:            1e-10  # The minimal time-step size of the simulation (in internal units).
+  dt_max:            1e-2  # The maximal time-step size of the simulation (in internal units).
+
+# Parameters governing the snapshots
+Snapshots:
+  subdir: snap
+  basename:   snapshot      # Common part of the name of output files
+  time_first: 0. # (Optional) Time of the first output if non-cosmological time-integration (in internal units)
+  delta_time: 1e-2        # Time difference between consecutive outputs (in internal units)
+
+Scheduler:
+  cell_extra_gparts: 10000      # (Optional) Number of spare sparts per top-level allocated at rebuild time for on-the-fly creation.
+  cell_extra_sinks: 10000       # (Optional) Number of spare sparts per top-level allocated at rebuild time for on-the-fly creation.
+  cell_extra_sparts: 10000      # (Optional) Number of spare sparts per top-level allocated at rebuild time for on-the-fly creation.
+  max_top_level_cells: 3        #
+  dependency_graph_frequency: 0  # (Optional) Dumping frequency of the dependency graph. By default, writes only at the first step.
+
+
+# Parameters governing the conserved quantities statistics
+Statistics:
+  delta_time:           1e-3     # Time between statistics output
+  time_first:             0.     # (Optional) Time of the first stats output if non-cosmological time-integration (in internal units)
+
+# Parameters related to the initial conditions
+InitialConditions:
+  file_name:          ics.hdf5
+  periodic:                    1    # Are we running with periodic ICs?
+
+# Parameters for the hydrodynamics scheme
+SPH:
+  resolution_eta:        1.2348   # Target smoothing length in units of the mean inter-particle separation (1.2348 == 57Ngbs with the Wendland C2 kernel).
+  # resolution_eta:        1.55   # Double the number of neighbours  
+  CFL_condition:         0.2      # Courant-Friedrich-Levy condition for time integration.
+  h_max:                 250.
+  minimal_temperature:   1
+
+BasicSink:
+  use_nibbling: 1
+  min_gas_mass_for_nibbling_Msun: 5e4 # half the gas particle mass in msun
diff --git a/examples/parameter_example.yml b/examples/parameter_example.yml
index d4d73eb8b8b2786425fd8e174a9fda35efa45181..0e3e172b407cb4a339e14dbea406fda75858994d 100644
--- a/examples/parameter_example.yml
+++ b/examples/parameter_example.yml
@@ -442,7 +442,7 @@ MWPotential2014Potential:
   Mdisk_Msun:       6.8e10                 # Mass of the disk (in M_sun)
   Rdisk_kpc:        3.0                    # Effective radius of the disk (in kpc)
   Zdisk_kpc:        0.280                  # Scale-height of the disk (in kpc)
-  amplitude:        1.0                    # Amplitude of the bulge
+  amplitude_Msun_per_kpc3: 1.0e10          # Amplitude of the bulge (in M_sun/kpc^3)
   r_1_kpc:          1.0                    # Reference radius for amplitude of the bulge (in kpc)
   alpha:            1.8                    # Exponent of the power law of the bulge
   r_c_kpc:          1.9                    # Cut-off radius of the bulge (in kpc)
@@ -877,9 +877,13 @@ XrayEmissivity:
 DefaultSink:
   cut_off_radius:        1e-3       # Cut off radius of the sink particles (in internal units). This parameter should be adapted with the resolution.
 
+BasicSink:
+  use_nibbling: 1                             # Use nibbling to accrete gas? If zero, do swallowing instead.
+  min_gas_mass_for_nibbling_Msun: 5e4         # The mass in Msun that particles cannot be nibbled below. A good default is half the initial mass.
+
 # GEAR sink particles
 GEARSink:
-  cut_off_radius:        1e-3                 # Cut off radius of the sink particles (in internal units). This parameter should be adapted with the resolution.
+  cut_off_radius:        1e-3                 # Cut off radius of the sink particles (in internal units). In GEAR, if this is specified, the cutoff radius is fixed, and the sink smoothing length is fixed at this value divided by kernel_gamma. If not, the cutoff radius varies with the sink smoothing length as r_cut = h*kernel_gamma.
   f_acc: 0.1                                  # (Optional) Fraction of the cut_off_radius that determines if a gas particle should be swallowed wihtout additional check. It has to respect 0 <= f_acc <= 1. (Default: 0.1)
   temperature_threshold_K:        100         # Max temperature (in K) for forming a sink when density_threshold_Hpcm3 <= density <= maximal_density_threshold_Hpcm3.
   density_threshold_Hpcm3: 1e3                # Minimum gas density (in Hydrogen atoms/cm3) required to form a sink particle.
diff --git a/src/Makefile.am b/src/Makefile.am
index 493a246f04e95e1fd6debee9f35179c31e8e9710..43047da7d9b733abb8671dc803b5cc791f9033bc 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -184,7 +184,7 @@ AM_SOURCES += output_options.c line_of_sight.c restart.c parser.c xmf.c
 AM_SOURCES += kernel_hydro.c tools.c map.c part.c partition.c clocks.c  
 AM_SOURCES += physical_constants.c units.c potential.c hydro_properties.c 
 AM_SOURCES += threadpool.c cooling.c star_formation.c 
-AM_SOURCES += hydro.c stars.c
+AM_SOURCES += hydro.c stars.c sink.c
 AM_SOURCES += statistics.c profiler.c csds.c part_type.c 
 AM_SOURCES += gravity_properties.c gravity.c multipole.c 
 AM_SOURCES += collectgroup.c hydro_space.c equation_of_state.c io_compression.c 
@@ -501,6 +501,8 @@ nobase_noinst_HEADERS += sink/Default/sink_iact.h sink/Default/sink_struct.h sin
 nobase_noinst_HEADERS += sink/GEAR/sink.h sink/GEAR/sink_io.h sink/GEAR/sink_part.h
 nobase_noinst_HEADERS += sink/GEAR/sink_properties.h sink/GEAR/sink_setters.h sink/GEAR/sink_getters.h
 nobase_noinst_HEADERS += sink/GEAR/sink_iact.h sink/GEAR/sink_struct.h sink/GEAR/sink_debug.h
+nobase_noinst_HEADERS += sink/Basic/sink.h sink/Basic/sink_io.h sink/Basic/sink_part.h sink/Basic/sink_properties.h
+nobase_noinst_HEADERS += sink/Basic/sink_iact.h sink/Basic/sink_struct.h sink/Basic/sink_debug.h
 nobase_noinst_HEADERS += neutrino.h neutrino_properties.h neutrino_io.h
 nobase_noinst_HEADERS += neutrino/Default/neutrino.h neutrino/Default/relativity.h neutrino/Default/fermi_dirac.h
 nobase_noinst_HEADERS += neutrino/Default/neutrino_properties.h neutrino/Default/neutrino_io.h
diff --git a/src/active.h b/src/active.h
index ae2c2423907f2e94b2e5073307efff5e228f21f9..324ab84d18d4faf0f57070f2fba5d7cd5df44221 100644
--- a/src/active.h
+++ b/src/active.h
@@ -196,7 +196,7 @@ __attribute__((always_inline)) INLINE static int cell_is_active_hydro(
  * @return 1 if the #cell contains at least an active particle, 0 otherwise.
  */
 __attribute__((always_inline)) INLINE static int cell_is_rt_active(
-    struct cell *c, const struct engine *e) {
+    const struct cell *c, const struct engine *e) {
 
 #ifdef SWIFT_DEBUG_CHECKS
   if (c->rt.ti_rt_end_min < e->ti_current_subcycle) {
diff --git a/src/black_holes/Default/black_holes_part.h b/src/black_holes/Default/black_holes_part.h
index d8e379dcec878993c41c222ef7bc6de2c9e755ab..5783279ad6f8328fbb43b46d56658c86ec2f7e4b 100644
--- a/src/black_holes/Default/black_holes_part.h
+++ b/src/black_holes/Default/black_holes_part.h
@@ -54,6 +54,9 @@ struct bpart {
   /*! Particle time bin */
   timebin_t time_bin;
 
+  /*! Tree-depth at which size / 2 <= h * gamma < size */
+  char depth_h;
+
   struct {
 
     /* Number of neighbours. */
diff --git a/src/black_holes/EAGLE/black_holes_part.h b/src/black_holes/EAGLE/black_holes_part.h
index 51bd44ec8b12efc4df8c9ebc614c8aa08a7eed78..48e7b7a3fb8f260c0c056c7b3771e403c951bd0a 100644
--- a/src/black_holes/EAGLE/black_holes_part.h
+++ b/src/black_holes/EAGLE/black_holes_part.h
@@ -62,6 +62,9 @@ struct bpart {
   /*! Particle time bin */
   timebin_t time_bin;
 
+  /*! Tree-depth at which size / 2 <= h * gamma < size */
+  char depth_h;
+
   struct {
 
     /* Number of neighbours. */
diff --git a/src/black_holes/SPIN_JET/black_holes_iact.h b/src/black_holes/SPIN_JET/black_holes_iact.h
index b9c214268b42cb961cab8b64ded15ec8c19d3dbd..dfa6ddd240f5f76e9633f6f12ba4e63b2f9ae550 100644
--- a/src/black_holes/SPIN_JET/black_holes_iact.h
+++ b/src/black_holes/SPIN_JET/black_holes_iact.h
@@ -53,7 +53,7 @@
  */
 __attribute__((always_inline)) INLINE static void
 runner_iact_nonsym_bh_gas_density(
-    const float r2, const float *dx, const float hi, const float hj,
+    const float r2, const float dx[3], const float hi, const float hj,
     struct bpart *bi, const struct part *pj, const struct xpart *xpj,
     const int with_cosmology, const struct cosmology *cosmo,
     const struct gravity_props *grav_props,
@@ -407,7 +407,7 @@ runner_iact_nonsym_bh_gas_density(
  */
 __attribute__((always_inline)) INLINE static void
 runner_iact_nonsym_bh_gas_repos(
-    const float r2, const float *dx, const float hi, const float hj,
+    const float r2, const float dx[3], const float hi, const float hj,
     struct bpart *bi, const struct part *pj, const struct xpart *xpj,
     const int with_cosmology, const struct cosmology *cosmo,
     const struct gravity_props *grav_props,
@@ -532,7 +532,7 @@ runner_iact_nonsym_bh_gas_repos(
  */
 __attribute__((always_inline)) INLINE static void
 runner_iact_nonsym_bh_gas_swallow(
-    const float r2, const float *dx, const float hi, const float hj,
+    const float r2, const float dx[3], const float hi, const float hj,
     struct bpart *bi, struct part *pj, struct xpart *xpj,
     const int with_cosmology, const struct cosmology *cosmo,
     const struct gravity_props *grav_props,
@@ -680,8 +680,8 @@ runner_iact_nonsym_bh_gas_swallow(
  * @param ti_current Current integer time value (for random numbers).
  */
 __attribute__((always_inline)) INLINE static void
-runner_iact_nonsym_bh_bh_repos(const float r2, const float *dx, const float hi,
-                               const float hj, struct bpart *bi,
+runner_iact_nonsym_bh_bh_repos(const float r2, const float dx[3],
+                               const float hi, const float hj, struct bpart *bi,
                                struct bpart *bj, const struct cosmology *cosmo,
                                const struct gravity_props *grav_props,
                                const struct black_holes_props *bh_props,
@@ -787,7 +787,7 @@ runner_iact_nonsym_bh_bh_repos(const float r2, const float *dx, const float hi,
  * @param ti_current Current integer time value (for random numbers).
  */
 __attribute__((always_inline)) INLINE static void
-runner_iact_nonsym_bh_bh_swallow(const float r2, const float *dx,
+runner_iact_nonsym_bh_bh_swallow(const float r2, const float dx[3],
                                  const float hi, const float hj,
                                  struct bpart *bi, struct bpart *bj,
                                  const struct cosmology *cosmo,
@@ -900,7 +900,7 @@ runner_iact_nonsym_bh_bh_swallow(const float r2, const float *dx,
  */
 __attribute__((always_inline)) INLINE static void
 runner_iact_nonsym_bh_gas_feedback(
-    const float r2, const float *dx, const float hi, const float hj,
+    const float r2, const float dx[3], const float hi, const float hj,
     const struct bpart *bi, struct part *pj, struct xpart *xpj,
     const int with_cosmology, const struct cosmology *cosmo,
     const struct gravity_props *grav_props,
diff --git a/src/black_holes/SPIN_JET/black_holes_part.h b/src/black_holes/SPIN_JET/black_holes_part.h
index 6e58a2088d1e20dd5310ed4636f9c91b94a3d18c..7e66da192bb937fea1d23c104c63e99d0d47f84c 100644
--- a/src/black_holes/SPIN_JET/black_holes_part.h
+++ b/src/black_holes/SPIN_JET/black_holes_part.h
@@ -63,6 +63,9 @@ struct bpart {
   /*! Particle time bin */
   timebin_t time_bin;
 
+  /*! Tree-depth at which size / 2 <= h * gamma < size */
+  char depth_h;
+
   struct {
 
     /* Number of neighbours. */
diff --git a/src/cell.c b/src/cell.c
index af23139a615730f75511497c35897e5e1193afd9..51f9f0c665d7a25d592e07236548414fec6f9f43 100644
--- a/src/cell.c
+++ b/src/cell.c
@@ -676,6 +676,18 @@ void cell_check_part_drift_point(struct cell *c, void *data) {
         c->hydro.parts[i].time_bin != time_bin_inhibited)
       error("part in an incorrect time-zone! p->ti_drift=%lld ti_drift=%lld",
             c->hydro.parts[i].ti_drift, ti_drift);
+
+  for (int i = 0; i < c->hydro.count; ++i) {
+    const struct part *p = &c->hydro.parts[i];
+    if (p->depth_h == c->depth) {
+      if (!(p->h >= c->h_min_allowed && p->h < c->h_max_allowed) && c->split) {
+        error(
+            "depth_h set incorrectly! c->depth=%d p->depth_h=%d h=%e h_min=%e "
+            "h_max=%e",
+            c->depth, p->depth_h, p->h, c->h_min_allowed, c->h_max_allowed);
+      }
+    }
+  }
 #else
   error("Calling debugging code without debugging flag activated.");
 #endif
@@ -750,6 +762,20 @@ void cell_check_sink_drift_point(struct cell *c, void *data) {
           "sink-part in an incorrect time-zone! sink->ti_drift=%lld "
           "ti_drift=%lld",
           c->sinks.parts[i].ti_drift, ti_drift);
+
+  for (int i = 0; i < c->sinks.count; ++i) {
+    const struct sink *sp = &c->sinks.parts[i];
+    if (sp->depth_h == c->depth) {
+      if (!(sp->h >= c->h_min_allowed && sp->h < c->h_max_allowed) &&
+          c->split) {
+        error(
+            "depth_h set incorrectly! c->depth=%d sp->depth_h=%d h=%e h_min=%e "
+            "h_max=%e",
+            c->depth, sp->depth_h, sp->h, c->h_min_allowed, c->h_max_allowed);
+      }
+    }
+  }
+
 #else
   error("Calling debugging code without debugging flag activated.");
 #endif
@@ -784,8 +810,69 @@ void cell_check_spart_drift_point(struct cell *c, void *data) {
   for (int i = 0; i < c->stars.count; ++i)
     if (c->stars.parts[i].ti_drift != ti_drift &&
         c->stars.parts[i].time_bin != time_bin_inhibited)
-      error("g-part in an incorrect time-zone! gp->ti_drift=%lld ti_drift=%lld",
+      error("s-part in an incorrect time-zone! sp->ti_drift=%lld ti_drift=%lld",
             c->stars.parts[i].ti_drift, ti_drift);
+
+  for (int i = 0; i < c->stars.count; ++i) {
+    const struct spart *p = &c->stars.parts[i];
+    if (p->depth_h == c->depth) {
+      if (!(p->h >= c->h_min_allowed && p->h < c->h_max_allowed) && c->split) {
+        error(
+            "depth_h set incorrectly! c->depth=%d p->depth_h=%d h=%e h_min=%e "
+            "h_max=%e",
+            c->depth, p->depth_h, p->h, c->h_min_allowed, c->h_max_allowed);
+      }
+    }
+  }
+#else
+  error("Calling debugging code without debugging flag activated.");
+#endif
+}
+
+/**
+ * @brief Checks that the #bpart in a cell are at the
+ * current point in time
+ *
+ * Calls error() if the cell is not at the current time.
+ *
+ * @param c Cell to act upon
+ * @param data The current time on the integer time-line
+ */
+void cell_check_bpart_drift_point(struct cell *c, void *data) {
+#ifdef SWIFT_DEBUG_CHECKS
+
+  const integertime_t ti_drift = *(integertime_t *)data;
+
+  /* Only check local cells */
+  if (c->nodeID != engine_rank) return;
+
+  /* Only check cells with content */
+  if (c->black_holes.count == 0) return;
+
+  if (c->black_holes.ti_old_part != ti_drift)
+    error(
+        "Cell in an incorrect time-zone! c->black_holes.ti_old_part=%lld "
+        "ti_drift=%lld",
+        c->black_holes.ti_old_part, ti_drift);
+
+  for (int i = 0; i < c->black_holes.count; ++i)
+    if (c->black_holes.parts[i].ti_drift != ti_drift &&
+        c->black_holes.parts[i].time_bin != time_bin_inhibited)
+      error("s-part in an incorrect time-zone! sp->ti_drift=%lld ti_drift=%lld",
+            c->black_holes.parts[i].ti_drift, ti_drift);
+
+  for (int i = 0; i < c->black_holes.count; ++i) {
+    const struct bpart *bp = &c->black_holes.parts[i];
+    if (bp->depth_h == c->depth) {
+      if (!(bp->h >= c->h_min_allowed && bp->h < c->h_max_allowed) &&
+          c->split) {
+        error(
+            "depth_h set incorrectly! c->depth=%d p->depth_h=%d h=%e h_min=%e "
+            "h_max=%e",
+            c->depth, bp->depth_h, bp->h, c->h_min_allowed, c->h_max_allowed);
+      }
+    }
+  }
 #else
   error("Calling debugging code without debugging flag activated.");
 #endif
diff --git a/src/cell.h b/src/cell.h
index 4843cc58c8f7270077562809015ad6dfe73e4a86..f3125c205cb3c8aa56e2a0e2da2153884b75b62c 100644
--- a/src/cell.h
+++ b/src/cell.h
@@ -203,7 +203,7 @@ struct pcell {
     int count;
 
     /*! Maximal cut off radius. */
-    float r_cut_max;
+    float h_max;
 
     /*! Minimal integer end-of-timestep in this cell for sinks tasks */
     integertime_t ti_end_min;
@@ -551,6 +551,14 @@ struct cell {
   /*! Minimum dimension, i.e. smallest edge of this cell (min(width)). */
   float dmin;
 
+  /*! When walking the tree and running loops at different level, this is
+   * the minimal h that can be processed at this level */
+  float h_min_allowed;
+
+  /*! When walking the tree and running loops at different level, this is
+   * the maximal h that can be processed at this level */
+  float h_max_allowed;
+
   /*! ID of the previous owner, e.g. runner. */
   short int owner;
 
@@ -560,9 +568,6 @@ struct cell {
   /*! ID of the node this cell lives on. */
   int nodeID;
 
-  /*! Number of tasks that are associated with this cell. */
-  short int nr_tasks;
-
   /*! The depth of this cell in the tree. */
   char depth;
 
@@ -585,6 +590,9 @@ struct cell {
 
 #ifdef SWIFT_DEBUG_CHECKS
 
+  /*! Number of tasks that are associated with this cell. */
+  short int nr_tasks;
+
   /*! The list of tasks that have been executed on this cell */
   char tasks_executed[task_type_count];
 
@@ -667,6 +675,7 @@ void cell_clean(struct cell *c);
 void cell_check_part_drift_point(struct cell *c, void *data);
 void cell_check_gpart_drift_point(struct cell *c, void *data);
 void cell_check_spart_drift_point(struct cell *c, void *data);
+void cell_check_bpart_drift_point(struct cell *c, void *data);
 void cell_check_sink_drift_point(struct cell *c, void *data);
 void cell_check_multipole_drift_point(struct cell *c, void *data);
 void cell_reset_task_counters(struct cell *c);
@@ -1157,7 +1166,7 @@ cell_can_recurse_in_pair_sinks_task(const struct cell *ci,
   /* smaller than the sub-cell sizes ? */
   /* Note: We use the _old values as these might have been updated by a drift */
   return ci->split && cj->split &&
-         ((ci->sinks.r_cut_max_old + ci->sinks.dx_max_part_old) <
+         ((kernel_gamma * ci->sinks.h_max_old + ci->sinks.dx_max_part_old) <
           0.5f * ci->dmin) &&
          ((kernel_gamma * cj->hydro.h_max_old + cj->hydro.dx_max_part_old) <
           0.5f * cj->dmin);
@@ -1210,7 +1219,7 @@ __attribute__((always_inline)) INLINE static int
 cell_can_recurse_in_self_sinks_task(const struct cell *c) {
 
   /* Is the cell split and not smaller than the smoothing length? */
-  return c->split && (c->sinks.r_cut_max_old < 0.5f * c->dmin) &&
+  return c->split && (kernel_gamma * c->sinks.h_max_old < 0.5f * c->dmin) &&
          (kernel_gamma * c->hydro.h_max_old < 0.5f * c->dmin);
 }
 
@@ -1420,7 +1429,7 @@ cell_need_rebuild_for_sinks_pair(const struct cell *ci, const struct cell *cj) {
   /* Is the cut-off radius plus the max distance the parts in both cells have */
   /* moved larger than the cell size ? */
   /* Note ci->dmin == cj->dmin */
-  if (max(ci->sinks.r_cut_max, kernel_gamma * cj->hydro.h_max) +
+  if (max(kernel_gamma * ci->sinks.h_max, kernel_gamma * cj->hydro.h_max) +
           ci->sinks.dx_max_part + cj->hydro.dx_max_part >
       cj->dmin) {
     return 1;
@@ -1924,6 +1933,147 @@ __attribute__((always_inline)) INLINE void cell_assign_cell_index(
 #endif
 }
 
+/**
+ * @brief Set the depth_h field of a #part.
+ *
+ * @param p The #part.
+ * @param leaf_cell The leaf cell where the particle is located.
+ */
+__attribute__((always_inline)) static INLINE void cell_set_part_h_depth(
+    struct part *p, const struct cell *leaf_cell) {
+
+  const float h = p->h;
+  const struct cell *c = leaf_cell;
+
+#ifdef SWIFT_DEBUG_CHECKS
+  if (leaf_cell->split) error("Running on an unsplit cell!");
+#endif
+
+  /* Case where h is much smaller than the leaf cell itself */
+  if (h < c->h_min_allowed) {
+    p->depth_h = c->depth;
+    return;
+  }
+
+  /* Climb the tree to find the correct level */
+  while (c != NULL) {
+    if (h >= c->h_min_allowed && h < c->h_max_allowed) {
+      p->depth_h = c->depth;
+      return;
+    }
+    c = c->parent;
+  }
+
+#ifdef SWIFT_DEBUG_CHECKS
+  error("Could not find an appropriate depth!");
+#endif
+}
+
+/**
+ * @brief Set the depth_h field of a #sink.
+ *
+ * @param sp The #sink.
+ * @param leaf_cell The leaf cell where the particle is located.
+ */
+__attribute__((always_inline)) static INLINE void cell_set_sink_h_depth(
+    struct sink *sp, const struct cell *leaf_cell) {
+
+  const float h = sp->h;
+  const struct cell *c = leaf_cell;
+
+#ifdef SWIFT_DEBUG_CHECKS
+  if (leaf_cell->split) error("Running on an unsplit cell!");
+#endif
+
+  /* Case where h is much smaller than the leaf cell itself */
+  if (h < c->h_min_allowed) {
+    sp->depth_h = c->depth;
+    return;
+  }
+
+  /* Climb the tree to find the correct level */
+  while (c != NULL) {
+    if (h >= c->h_min_allowed && h < c->h_max_allowed) {
+      sp->depth_h = c->depth;
+      return;
+    }
+    c = c->parent;
+  }
+#ifdef SWIFT_DEBUG_CHECKS
+  error("Could not find an appropriate depth!");
+#endif
+}
+
+/**
+ * @brief Set the depth_h field of a #spart.
+ *
+ * @param sp The #spart.
+ * @param leaf_cell The leaf cell where the particle is located.
+ */
+__attribute__((always_inline)) static INLINE void cell_set_spart_h_depth(
+    struct spart *sp, const struct cell *leaf_cell) {
+
+  const float h = sp->h;
+  const struct cell *c = leaf_cell;
+
+#ifdef SWIFT_DEBUG_CHECKS
+  if (leaf_cell->split) error("Running on an unsplit cell!");
+#endif
+
+  /* Case where h is much smaller than the leaf cell itself */
+  if (h < c->h_min_allowed) {
+    sp->depth_h = c->depth;
+    return;
+  }
+
+  /* Climb the tree to find the correct level */
+  while (c != NULL) {
+    if (h >= c->h_min_allowed && h < c->h_max_allowed) {
+      sp->depth_h = c->depth;
+      return;
+    }
+    c = c->parent;
+  }
+#ifdef SWIFT_DEBUG_CHECKS
+  error("Could not find an appropriate depth!");
+#endif
+}
+
+/**
+ * @brief Set the depth_h field of a #bpart.
+ *
+ * @param bp The #bpart.
+ * @param leaf_cell The leaf cell where the particle is located.
+ */
+__attribute__((always_inline)) static INLINE void cell_set_bpart_h_depth(
+    struct bpart *bp, const struct cell *leaf_cell) {
+
+  const float h = bp->h;
+  const struct cell *c = leaf_cell;
+
+#ifdef SWIFT_DEBUG_CHECKS
+  if (leaf_cell->split) error("Running on an unsplit cell!");
+#endif
+
+  /* Case where h is much smaller than the leaf cell itself */
+  if (h < c->h_min_allowed) {
+    bp->depth_h = c->depth;
+    return;
+  }
+
+  /* Climb the tree to find the correct level */
+  while (c != NULL) {
+    if (h >= c->h_min_allowed && h < c->h_max_allowed) {
+      bp->depth_h = c->depth;
+      return;
+    }
+    c = c->parent;
+  }
+#ifdef SWIFT_DEBUG_CHECKS
+  error("Could not find an appropriate depth!");
+#endif
+}
+
 /**
  * @brief Does this cell overlap the zoom region?
  *
@@ -1988,5 +2138,4 @@ __attribute__((always_inline)) INLINE static int zoom_cell_inside_zoom_region(
           cell_min[2] >= zoom_min[2] && cell_max[0] <= zoom_max[0] &&
           cell_max[1] <= zoom_max[1] && cell_max[2] <= zoom_max[2]);
 }
-
 #endif /* SWIFT_CELL_H */
diff --git a/src/cell_convert_part.c b/src/cell_convert_part.c
index 77b30e1ccd876314f5a9adace920683a186c825b..4312870e88a68c78cf10a83874cacda4eab26a2d 100644
--- a/src/cell_convert_part.c
+++ b/src/cell_convert_part.c
@@ -266,6 +266,9 @@ struct spart *cell_add_spart(struct engine *e, struct cell *const c) {
   sp->ti_drift = e->ti_current;
 #endif
 
+  /* Give the new particle the correct depth */
+  cell_set_spart_h_depth(sp, c);
+
   /* Register that we used one of the free slots. */
   const size_t one = 1;
   atomic_sub(&e->s->nr_extra_sparts, one);
@@ -404,6 +407,9 @@ struct sink *cell_add_sink(struct engine *e, struct cell *const c) {
   sp->ti_drift = e->ti_current;
 #endif
 
+  /* Give the new particle the correct depth */
+  cell_set_sink_h_depth(sp, c);
+
   /* Register that we used one of the free slots. */
   const size_t one = 1;
   atomic_sub(&e->s->nr_extra_sinks, one);
@@ -1189,7 +1195,7 @@ struct spart *cell_spawn_new_spart_from_sink(struct engine *e, struct cell *c,
 #endif
 
   /* Set a smoothing length */
-  sp->h = s->r_cut;
+  sp->h = s->h;
 
   /* Here comes the Sun! */
   return sp;
diff --git a/src/cell_drift.c b/src/cell_drift.c
index 3b4a7067a08a01aba4f00833bd447ca4c39779dc..d837ca2dbc850051203eb8335d0adf44bc9e9ab1 100644
--- a/src/cell_drift.c
+++ b/src/cell_drift.c
@@ -280,6 +280,9 @@ void cell_drift_part(struct cell *c, const struct engine *e, int force,
                           with_cosmology, e->cosmology, e->hydro_properties,
                           e->cooling_func, e->time);
 
+      /* Set the appropriate depth level for this particle */
+      cell_set_part_h_depth(p, c);
+
 #ifdef SWIFT_DEBUG_CHECKS
       /* Make sure the particle does not drift by more than a box length. */
       if (fabs(xp->v_full[0] * dt_drift) > e->s->dim[0] ||
@@ -763,6 +766,9 @@ void cell_drift_spart(struct cell *c, const struct engine *e, int force,
       drift_spart(sp, dt_drift, ti_old_spart, ti_current, e, replication_list,
                   c->loc);
 
+      /* Set the appropriate depth level for this particle */
+      cell_set_spart_h_depth(sp, c);
+
 #ifdef SWIFT_DEBUG_CHECKS
       /* Make sure the particle does not drift by more than a box length. */
       if (fabs(sp->v[0] * dt_drift) > e->s->dim[0] ||
@@ -1052,6 +1058,9 @@ void cell_drift_bpart(struct cell *c, const struct engine *e, int force,
       drift_bpart(bp, dt_drift, ti_old_bpart, ti_current, e, replication_list,
                   c->loc);
 
+      /* Set the appropriate depth level for this particle */
+      cell_set_bpart_h_depth(bp, c);
+
 #ifdef SWIFT_DEBUG_CHECKS
       /* Make sure the particle does not drift by more than a box length. */
       if (fabs(bp->v[0] * dt_drift) > e->s->dim[0] ||
@@ -1238,8 +1247,8 @@ void cell_drift_sink(struct cell *c, const struct engine *e, int force) {
   struct sink *const sinks = c->sinks.parts;
 
   float dx_max = 0.f, dx2_max = 0.f;
-  float cell_r_max = 0.f;
-  float cell_r_max_active = 0.f;
+  float cell_h_max = 0.f;
+  float cell_h_max_active = 0.f;
 
   /* Drift irrespective of cell flags? */
   force = (force || cell_get_flag(c, cell_flag_do_sink_drift));
@@ -1279,14 +1288,14 @@ void cell_drift_sink(struct cell *c, const struct engine *e, int force) {
 
         /* Update */
         dx_max = max(dx_max, cp->sinks.dx_max_part);
-        cell_r_max = max(cell_r_max, cp->sinks.r_cut_max);
-        cell_r_max_active = max(cell_r_max_active, cp->sinks.r_cut_max_active);
+        cell_h_max = max(cell_h_max, cp->sinks.h_max);
+        cell_h_max_active = max(cell_h_max_active, cp->sinks.h_max_active);
       }
     }
 
     /* Store the values */
-    c->sinks.r_cut_max = cell_r_max;
-    c->sinks.r_cut_max_active = cell_r_max_active;
+    c->sinks.h_max = cell_h_max;
+    c->sinks.h_max_active = cell_h_max_active;
     c->sinks.dx_max_part = dx_max;
 
     /* Update the time of the last drift */
@@ -1316,6 +1325,9 @@ void cell_drift_sink(struct cell *c, const struct engine *e, int force) {
       /* Drift... */
       drift_sink(sink, dt_drift, ti_old_sink, ti_current);
 
+      /* Set the appropriate depth level for this particle */
+      cell_set_sink_h_depth(sink, c);
+
 #ifdef SWIFT_DEBUG_CHECKS
       /* Make sure the particle does not drift by more than a box length. */
       if (fabs(sink->v[0] * dt_drift) > e->s->dim[0] ||
@@ -1409,7 +1421,7 @@ void cell_drift_sink(struct cell *c, const struct engine *e, int force) {
       dx2_max = max(dx2_max, dx2);
 
       /* Maximal smoothing length */
-      cell_r_max = max(cell_r_max, sink->r_cut);
+      cell_h_max = max(cell_h_max, sink->h);
 
       /* Mark the particle has not being swallowed */
       sink_mark_sink_as_not_swallowed(&sink->merger_data);
@@ -1418,7 +1430,7 @@ void cell_drift_sink(struct cell *c, const struct engine *e, int force) {
       if (sink_is_active(sink, e)) {
         sink_init_sink(sink);
 
-        cell_r_max_active = max(cell_r_max_active, sink->r_cut);
+        cell_h_max_active = max(cell_h_max_active, sink->h);
       }
     }
 
@@ -1426,8 +1438,8 @@ void cell_drift_sink(struct cell *c, const struct engine *e, int force) {
     dx_max = sqrtf(dx2_max);
 
     /* Store the values */
-    c->sinks.r_cut_max = cell_r_max;
-    c->sinks.r_cut_max_active = cell_r_max_active;
+    c->sinks.h_max = cell_h_max;
+    c->sinks.h_max_active = cell_h_max_active;
     c->sinks.dx_max_part = dx_max;
 
     /* Update the time of the last drift */
diff --git a/src/cell_hydro.h b/src/cell_hydro.h
index 39db7bc21934e434583551de2a693da46fc7cacc..73d9a5e239b0aa227434846231775fd344ba0988 100644
--- a/src/cell_hydro.h
+++ b/src/cell_hydro.h
@@ -106,6 +106,9 @@ struct cell_hydro {
     /*! Last (integer) time the cell's part were drifted forward in time. */
     integertime_t ti_old_part;
 
+    /*! Spin-lock for the case where we do an extra sort of the cell. */
+    swift_lock_type extra_sort_lock;
+
     /*! Max smoothing length of active particles in this cell. */
     float h_max_active;
 
diff --git a/src/cell_pack.c b/src/cell_pack.c
index 83f8c4cab9d9c9de3cddfc906f789c3e0d2b2384..c806fa95a5cc07fec49ab09cb9107a070b100515 100644
--- a/src/cell_pack.c
+++ b/src/cell_pack.c
@@ -44,7 +44,7 @@ int cell_pack(struct cell *restrict c, struct pcell *restrict pc,
   pc->hydro.h_max = c->hydro.h_max;
   pc->stars.h_max = c->stars.h_max;
   pc->black_holes.h_max = c->black_holes.h_max;
-  pc->sinks.r_cut_max = c->sinks.r_cut_max;
+  pc->sinks.h_max = c->sinks.h_max;
 
   pc->hydro.ti_end_min = c->hydro.ti_end_min;
   pc->grav.ti_end_min = c->grav.ti_end_min;
@@ -246,7 +246,7 @@ int cell_unpack(struct pcell *restrict pc, struct cell *restrict c,
   c->hydro.h_max = pc->hydro.h_max;
   c->stars.h_max = pc->stars.h_max;
   c->black_holes.h_max = pc->black_holes.h_max;
-  c->sinks.r_cut_max = pc->sinks.r_cut_max;
+  c->sinks.h_max = pc->sinks.h_max;
 
   c->hydro.ti_end_min = pc->hydro.ti_end_min;
   c->grav.ti_end_min = pc->grav.ti_end_min;
@@ -311,6 +311,8 @@ int cell_unpack(struct pcell *restrict pc, struct cell *restrict c,
       temp->width[1] = c->width[1] / 2;
       temp->width[2] = c->width[2] / 2;
       temp->dmin = c->dmin / 2;
+      temp->h_min_allowed = temp->dmin * 0.5 * (1. / kernel_gamma);
+      temp->h_max_allowed = temp->dmin * (1. / kernel_gamma);
       if (k & 4) temp->loc[0] += temp->width[0];
       if (k & 2) temp->loc[1] += temp->width[1];
       if (k & 1) temp->loc[2] += temp->width[2];
diff --git a/src/cell_sinks.h b/src/cell_sinks.h
index 9043ad191552657c7183b35ec7d443207f4af384..2a8bb3c6a19a61a03525a3a5230b3a5333379d0c 100644
--- a/src/cell_sinks.h
+++ b/src/cell_sinks.h
@@ -91,11 +91,12 @@ struct cell_sinks {
     /*! Nr of #sink this cell can hold after addition of new one. */
     int count_total;
 
-    /*! Max cut off radius of active particles in this cell. */
-    float r_cut_max_active;
+    /*! Max smoothing length of active particles in this cell.
+     */
+    float h_max_active;
 
-    /*! Values of r_cut_max before the drifts, used for sub-cell tasks. */
-    float r_cut_max_old;
+    /*! Values of h_max before the drifts, used for sub-cell tasks. */
+    float h_max_old;
 
     /*! Maximum part movement in this cell since last construction. */
     float dx_max_part;
@@ -117,8 +118,8 @@ struct cell_sinks {
   /*! Spin lock for various uses (#sink case). */
   swift_lock_type lock;
 
-  /*! Max cut off radius in this cell. */
-  float r_cut_max;
+  /*! Max smoothing length in this cell. */
+  float h_max;
 
   /*! Number of #sink updated in this cell. */
   int updated;
diff --git a/src/cell_unskip.c b/src/cell_unskip.c
index 3a6f5fd04dd63669f74c800ad5400a1c6d8544e0..a84c77c6c771870f54e123135bcdfb125f61f979 100644
--- a/src/cell_unskip.c
+++ b/src/cell_unskip.c
@@ -1151,13 +1151,13 @@ void cell_activate_subcell_sinks_tasks(struct cell *ci, struct cell *cj,
 
   /* Store the current dx_max and h_max values. */
   ci->sinks.dx_max_part_old = ci->sinks.dx_max_part;
-  ci->sinks.r_cut_max_old = ci->sinks.r_cut_max;
+  ci->sinks.h_max_old = ci->sinks.h_max;
   ci->hydro.dx_max_part_old = ci->hydro.dx_max_part;
   ci->hydro.h_max_old = ci->hydro.h_max;
 
   if (cj != NULL) {
     cj->sinks.dx_max_part_old = cj->sinks.dx_max_part;
-    cj->sinks.r_cut_max_old = cj->sinks.r_cut_max;
+    cj->sinks.h_max_old = cj->sinks.h_max;
     cj->hydro.dx_max_part_old = cj->hydro.dx_max_part;
     cj->hydro.h_max_old = cj->hydro.h_max;
   }
diff --git a/src/chemistry/AGORA/chemistry_iact.h b/src/chemistry/AGORA/chemistry_iact.h
index 8b433f71dd90fc547256e564ee5a144ff1bd2358..f8e99a49d4e08c573b143bdf9d1b350d57a392eb 100644
--- a/src/chemistry/AGORA/chemistry_iact.h
+++ b/src/chemistry/AGORA/chemistry_iact.h
@@ -122,9 +122,9 @@ __attribute__((always_inline)) INLINE static void runner_iact_nonsym_chemistry(
  *
  */
 __attribute__((always_inline)) INLINE static void runner_iact_diffusion(
-    float r2, const float *dx, float hi, float hj, struct part *restrict pi,
-    struct part *restrict pj, const float a, const float H,
-    const float time_base, const integertime_t t_current,
+    const float r2, const float dx[3], const float hi, const float hj,
+    struct part *restrict pi, struct part *restrict pj, const float a,
+    const float H, const float time_base, const integertime_t t_current,
     const struct cosmology *cosmo, const int with_cosmology) {}
 
 /**
@@ -147,9 +147,9 @@ __attribute__((always_inline)) INLINE static void runner_iact_diffusion(
  *
  */
 __attribute__((always_inline)) INLINE static void runner_iact_nonsym_diffusion(
-    float r2, const float *dx, float hi, float hj, struct part *restrict pi,
-    struct part *restrict pj, const float a, const float H,
-    const float time_base, const integertime_t t_current,
+    const float r2, const float dx[3], const float hi, const float hj,
+    struct part *restrict pi, struct part *restrict pj, const float a,
+    const float H, const float time_base, const integertime_t t_current,
     const struct cosmology *cosmo, const int with_cosmology) {}
 
 #endif /* SWIFT_AGORA_CHEMISTRY_IACT_H */
diff --git a/src/chemistry/GEAR/chemistry.h b/src/chemistry/GEAR/chemistry.h
index 304809fe0b44fc2bd39fdd4bea00495fd4f24431..f9c4282e04cc999d7560cca0559f39bd1216abdb 100644
--- a/src/chemistry/GEAR/chemistry.h
+++ b/src/chemistry/GEAR/chemistry.h
@@ -105,8 +105,9 @@ INLINE static void chemistry_copy_sink_properties(const struct part* p,
  */
 static INLINE void chemistry_print_backend(
     const struct chemistry_global_data* data) {
-
-  message("Chemistry function is 'Gear'.");
+  if (engine_rank == 0) {
+    message("Chemistry function is 'Gear'.");
+  }
 }
 
 /**
@@ -283,10 +284,12 @@ static INLINE void chemistry_init_backend(struct swift_params* parameter_file,
   const float initial_metallicity = parser_get_param_float(
       parameter_file, "GEARChemistry:initial_metallicity");
 
-  if (initial_metallicity < 0) {
-    message("Setting the initial metallicity from the snapshot.");
-  } else {
-    message("Setting the initial metallicity from the parameter file.");
+  if (engine_rank == 0) {
+    if (initial_metallicity < 0) {
+      message("Setting the initial metallicity from the snapshot.");
+    } else {
+      message("Setting the initial metallicity from the parameter file.");
+    }
   }
 
   /* Set the initial metallicities */
diff --git a/src/chemistry/none/chemistry_iact.h b/src/chemistry/none/chemistry_iact.h
index f662881c8f7633d4cdf74981328d0fec4d4d1df6..ffa4b3f51463ea34b76331f1fd16e6001195aca8 100644
--- a/src/chemistry/none/chemistry_iact.h
+++ b/src/chemistry/none/chemistry_iact.h
@@ -39,8 +39,9 @@
  * @param H Current Hubble parameter.
  */
 __attribute__((always_inline)) INLINE static void runner_iact_chemistry(
-    float r2, const float *dx, float hi, float hj, struct part *restrict pi,
-    struct part *restrict pj, const float a, const float H) {}
+    const float r2, const float dx[3], const float hi, const float hj,
+    struct part *restrict pi, struct part *restrict pj, const float a,
+    const float H) {}
 
 /**
  * @brief do chemistry computation after the runner_iact_density (non symmetric
@@ -56,8 +57,9 @@ __attribute__((always_inline)) INLINE static void runner_iact_chemistry(
  * @param H Current Hubble parameter.
  */
 __attribute__((always_inline)) INLINE static void runner_iact_nonsym_chemistry(
-    float r2, const float *dx, float hi, float hj, struct part *restrict pi,
-    const struct part *restrict pj, const float a, const float H) {}
+    const float r2, const float dx[3], const float hi, const float hj,
+    struct part *restrict pi, const struct part *restrict pj, const float a,
+    const float H) {}
 
 /**
  * @brief do metal diffusion computation in the <FORCE LOOP>
@@ -79,9 +81,9 @@ __attribute__((always_inline)) INLINE static void runner_iact_nonsym_chemistry(
  *
  */
 __attribute__((always_inline)) INLINE static void runner_iact_diffusion(
-    float r2, const float *dx, float hi, float hj, struct part *restrict pi,
-    struct part *restrict pj, const float a, const float H,
-    const float time_base, const integertime_t t_current,
+    const float r2, const float dx[3], const float hi, const float hj,
+    struct part *restrict pi, struct part *restrict pj, const float a,
+    const float H, const float time_base, const integertime_t t_current,
     const struct cosmology *cosmo, const int with_cosmology) {}
 
 /**
@@ -104,9 +106,9 @@ __attribute__((always_inline)) INLINE static void runner_iact_diffusion(
  *
  */
 __attribute__((always_inline)) INLINE static void runner_iact_nonsym_diffusion(
-    float r2, const float *dx, float hi, float hj, struct part *restrict pi,
-    struct part *restrict pj, const float a, const float H,
-    const float time_base, const integertime_t t_current,
+    const float r2, const float dx[3], const float hi, const float hj,
+    struct part *restrict pi, struct part *restrict pj, const float a,
+    const float H, const float time_base, const integertime_t t_current,
     const struct cosmology *cosmo, const int with_cosmology) {}
 
 #endif /* SWIFT_NONE_CHEMISTRY_IACT_H */
diff --git a/src/cooling/grackle/cooling.c b/src/cooling/grackle/cooling.c
index be7d417259654ccdf370fb644de1db012f5f683a..7b764728bd98c19e8c61312e30522a744569f305 100644
--- a/src/cooling/grackle/cooling.c
+++ b/src/cooling/grackle/cooling.c
@@ -434,6 +434,10 @@ float cooling_get_radiated_energy(const struct xpart* xp) {
  */
 void cooling_print_backend(const struct cooling_function_data* cooling) {
 
+  if (engine_rank != 0) {
+    return;
+  }
+
   message("Cooling function is 'Grackle'.");
   message("Using Grackle = %i", cooling->chemistry_data.use_grackle);
   message("Chemical network = %i",
diff --git a/src/debug.c b/src/debug.c
index a82363365c9004d27ca6b8fbb007e46d9fa89e64..0b1538b5c4c72e1b292fb29e57a6526c38a4ff7a 100644
--- a/src/debug.c
+++ b/src/debug.c
@@ -242,8 +242,8 @@ int checkSpacehmax(struct space *s) {
   float cell_sinks_h_max = 0.0f;
   for (int k = 0; k < s->nr_cells; k++) {
     if (s->cells_top[k].nodeID == s->e->nodeID &&
-        s->cells_top[k].sinks.r_cut_max > cell_sinks_h_max) {
-      cell_sinks_h_max = s->cells_top[k].sinks.r_cut_max;
+        s->cells_top[k].sinks.h_max > cell_sinks_h_max) {
+      cell_sinks_h_max = s->cells_top[k].sinks.h_max;
     }
   }
 
@@ -266,8 +266,8 @@ int checkSpacehmax(struct space *s) {
   /* Now all the sinks. */
   float sink_h_max = 0.0f;
   for (size_t k = 0; k < s->nr_sinks; k++) {
-    if (s->sinks[k].r_cut > sink_h_max) {
-      sink_h_max = s->sinks[k].r_cut;
+    if (s->sinks[k].h > sink_h_max) {
+      sink_h_max = s->sinks[k].h;
     }
   }
 
@@ -315,17 +315,17 @@ int checkSpacehmax(struct space *s) {
   /* sink */
   for (int k = 0; k < s->nr_cells; k++) {
     if (s->cells_top[k].nodeID == s->e->nodeID) {
-      if (s->cells_top[k].sinks.r_cut_max > sink_h_max) {
+      if (s->cells_top[k].sinks.h_max > sink_h_max) {
         message("cell %d is inconsistent (%f > %f)", k,
-                s->cells_top[k].sinks.r_cut_max, sink_h_max);
+                s->cells_top[k].sinks.h_max, sink_h_max);
       }
     }
   }
 
   for (size_t k = 0; k < s->nr_sinks; k++) {
-    if (s->sinks[k].r_cut > cell_sinks_h_max) {
+    if (s->sinks[k].h > cell_sinks_h_max) {
       message("spart %lld is inconsistent (%f > %f)", s->sinks[k].id,
-              s->sinks[k].r_cut, cell_sinks_h_max);
+              s->sinks[k].h, cell_sinks_h_max);
     }
   }
 
@@ -436,7 +436,7 @@ int checkCellhdxmax(const struct cell *c, int *depth) {
                       sp->x_diff[1] * sp->x_diff[1] +
                       sp->x_diff[2] * sp->x_diff[2];
 
-    sinks_h_max = max(sinks_h_max, sp->r_cut);
+    sinks_h_max = max(sinks_h_max, sp->h);
     sinks_dx_max = max(sinks_dx_max, sqrtf(dx2));
   }
 
@@ -476,9 +476,9 @@ int checkCellhdxmax(const struct cell *c, int *depth) {
     result = 0;
   }
 
-  if (c->sinks.r_cut_max != sinks_h_max) {
+  if (c->sinks.h_max != sinks_h_max) {
     message("%d Inconsistent sinks_h_max: cell %f != parts %f", *depth,
-            c->sinks.r_cut_max, sinks_h_max);
+            c->sinks.h_max, sinks_h_max);
     message("location: %f %f %f", c->loc[0], c->loc[1], c->loc[2]);
     result = 0;
   }
diff --git a/src/engine.c b/src/engine.c
index 665493cbb36f50e0e59989c15c24d471c5c3b889..4d7f92f31e8a5dfddda68548380e408456a062de 100644
--- a/src/engine.c
+++ b/src/engine.c
@@ -1302,6 +1302,19 @@ void engine_print_task_counts(const struct engine *e) {
   }
 #endif /* SWIFT_DEBUG_CHECKS */
 
+#if defined(SWIFT_DEBUG_CHECKS) && defined(WITH_MPI)
+  if (e->verbose == 2) {
+    /* check that the global number of sends matches the global number of
+       recvs */
+    int global_counts[2] = {counts[task_type_send], counts[task_type_recv]};
+    MPI_Allreduce(MPI_IN_PLACE, global_counts, 2, MPI_INT, MPI_SUM,
+                  MPI_COMM_WORLD);
+    if (global_counts[0] != global_counts[1])
+      error("Missing communications (%i sends, %i recvs)!", global_counts[0],
+            global_counts[1]);
+  }
+#endif
+
   if (e->verbose)
     message("took %.3f %s.", clocks_from_ticks(getticks() - tic),
             clocks_getunit());
@@ -2130,6 +2143,47 @@ void engine_get_max_ids(struct engine *e) {
 #endif
 }
 
+/**
+ * @brief Gather the information about the top-level cells whose time-step has
+ * changed and activate the communications required to synchonize the
+ * time-steps.
+ *
+ * @param e The #engine.
+ */
+void engine_synchronize_times(struct engine *e) {
+
+#ifdef WITH_MPI
+
+  const ticks tic = getticks();
+
+  /* Collect which top-level cells have been updated */
+  MPI_Allreduce(MPI_IN_PLACE, e->s->cells_top_updated, e->s->nr_cells, MPI_CHAR,
+                MPI_SUM, MPI_COMM_WORLD);
+
+  /* Activate tend communications involving the cells that have changed. */
+  for (int i = 0; i < e->s->nr_cells; ++i) {
+
+    if (e->s->cells_top_updated[i]) {
+
+      struct cell *c = &e->s->cells_top[i];
+      scheduler_activate_all_subtype(&e->sched, c->mpi.send, task_subtype_tend);
+      scheduler_activate_all_subtype(&e->sched, c->mpi.recv, task_subtype_tend);
+    }
+  }
+
+  if (e->verbose)
+    message("Gathering and activating tend took %.3f %s.",
+            clocks_from_ticks(getticks() - tic), clocks_getunit());
+
+  TIMER_TIC;
+  engine_launch(e, "tend");
+  TIMER_TOC(timer_runners);
+
+#else
+  error("SWIFT was not compiled with MPI support.");
+#endif
+}
+
 /**
  * @brief Run the radiative transfer sub-cycles outside the
  * regular time-steps.
@@ -2422,6 +2476,9 @@ void engine_init_particles(struct engine *e, int flag_entropy_ICs,
   }
 #endif
 
+  /* Zero the list of cells that have had their time-step updated */
+  bzero(e->s->cells_top_updated, e->s->nr_cells * sizeof(char));
+
   /* Now, launch the calculation */
   TIMER_TIC;
   engine_launch(e, "tasks");
@@ -2516,11 +2573,19 @@ void engine_init_particles(struct engine *e, int flag_entropy_ICs,
   scheduler_write_cell_dependencies(&e->sched, e->verbose, e->step);
   if (e->nodeID == 0) scheduler_write_task_level(&e->sched, e->step);
 
+  /* Zero the list of cells that have had their time-step updated */
+  bzero(e->s->cells_top_updated, e->s->nr_cells * sizeof(char));
+
   /* Run the 0th time-step */
   TIMER_TIC2;
   engine_launch(e, "tasks");
   TIMER_TOC2(timer_runners);
 
+  /* When running over MPI, synchronize top-level cells */
+#ifdef WITH_MPI
+  engine_synchronize_times(e);
+#endif
+
 #ifdef SWIFT_HYDRO_DENSITY_CHECKS
   /* Run the brute-force hydro calculation for some parts */
   if (e->policy & engine_policy_hydro)
@@ -2540,6 +2605,15 @@ void engine_init_particles(struct engine *e, int flag_entropy_ICs,
     stars_exact_density_check(e->s, e, /*rel_tol=*/1e-3);
 #endif
 
+#ifdef SWIFT_SINK_DENSITY_CHECKS
+  /* Run the brute-force sink calculation for some sinks */
+  if (e->policy & engine_policy_sinks) sink_exact_density_compute(e->s, e);
+
+  /* Check the accuracy of the sink calculation */
+  if (e->policy & engine_policy_sinks)
+    sink_exact_density_check(e->s, e, /*rel_tol=*/1e-3);
+#endif
+
 #ifdef SWIFT_GRAVITY_FORCE_CHECKS
   /* Check the accuracy of the gravity calculation */
   if (e->policy & engine_policy_self_gravity)
@@ -2652,12 +2726,12 @@ void engine_init_particles(struct engine *e, int flag_entropy_ICs,
     for (int i = 0; i < s->nr_cells; i++) {
       struct cell *c = &s->cells_top[i];
       if (c->nodeID == engine_rank && c->sinks.count > 0) {
-        float sink_h_max = c->sinks.parts[0].r_cut;
+        float sink_h_max = c->sinks.parts[0].h;
         for (int k = 1; k < c->sinks.count; k++) {
-          if (c->sinks.parts[k].r_cut > sink_h_max)
-            sink_h_max = c->sinks.parts[k].r_cut;
+          if (c->sinks.parts[k].h > sink_h_max)
+            sink_h_max = c->sinks.parts[k].h;
         }
-        c->sinks.r_cut_max = max(sink_h_max, c->sinks.r_cut_max);
+        c->sinks.h_max = max(sink_h_max, c->sinks.h_max);
       }
     }
   }
@@ -3048,11 +3122,19 @@ int engine_step(struct engine *e) {
      want to lose the data from the tasks) */
   space_reset_ghost_histograms(e->s);
 
+  /* Zero the list of cells that have had their time-step updated */
+  bzero(e->s->cells_top_updated, e->s->nr_cells * sizeof(char));
+
   /* Start all the tasks. */
   TIMER_TIC;
   engine_launch(e, "tasks");
   TIMER_TOC(timer_runners);
 
+  /* When running over MPI, synchronize top-level cells */
+#ifdef WITH_MPI
+  engine_synchronize_times(e);
+#endif
+
   /* Now record the CPU times used by the tasks. */
 #ifdef WITH_MPI
   double end_usertime = 0.0;
@@ -3081,6 +3163,15 @@ int engine_step(struct engine *e) {
     stars_exact_density_check(e->s, e, /*rel_tol=*/1e-2);
 #endif
 
+#ifdef SWIFT_SINK_DENSITY_CHECKS
+  /* Run the brute-force sink calculation for some sinks */
+  if (e->policy & engine_policy_sinks) sink_exact_density_compute(e->s, e);
+
+  /* Check the accuracy of the sink calculation */
+  if (e->policy & engine_policy_sinks)
+    sink_exact_density_check(e->s, e, /*rel_tol=*/1e-2);
+#endif
+
 #ifdef SWIFT_GRAVITY_FORCE_CHECKS
   /* Check if we want to run force checks this timestep. */
   if (e->policy & engine_policy_self_gravity) {
@@ -3158,9 +3249,9 @@ int engine_step(struct engine *e) {
             e->collect_group1.csds_file_size_gb);
 #endif
 
-    /********************************************************/
-    /* OK, we are done with the regular stuff. Time for i/o */
-    /********************************************************/
+  /********************************************************/
+  /* OK, we are done with the regular stuff. Time for i/o */
+  /********************************************************/
 
 #ifdef WITH_LIGHTCONE
   /* Flush lightcone buffers if necessary */
diff --git a/src/engine_config.c b/src/engine_config.c
index e6ba9795e5bdfd14262b2dd6426483d275111ce6..bba56a0e52f6210733c7af2ffe6aefa0f4abf61c 100644
--- a/src/engine_config.c
+++ b/src/engine_config.c
@@ -1061,7 +1061,7 @@ void engine_config(int restart, int fof, struct engine *e,
   if (e->policy & engine_policy_structure_finding) velociraptor_init(e);
 #endif
 
-    /* Free the affinity stuff */
+  /* Free the affinity stuff */
 #if defined(HAVE_SETAFFINITY)
   if (with_aff) {
     free(cpuid);
diff --git a/src/engine_maketasks.c b/src/engine_maketasks.c
index 253473ef47639700b2d91ab16f22b3262260e2fb..943b67584f21b6f1ba526a0e5ee2340382374c2e 100644
--- a/src/engine_maketasks.c
+++ b/src/engine_maketasks.c
@@ -2297,7 +2297,9 @@ void engine_count_and_link_tasks_mapper(void *map_data, int num_elements,
 
       /* Link self tasks to cells. */
     } else if (t_type == task_type_self) {
+#ifdef SWIFT_DEBUG_CHECKS
       atomic_inc(&ci->nr_tasks);
+#endif
 
       if (t_subtype == task_subtype_density) {
         engine_addlink(e, &ci->hydro.density, t);
@@ -2309,8 +2311,10 @@ void engine_count_and_link_tasks_mapper(void *map_data, int num_elements,
 
       /* Link pair tasks to cells. */
     } else if (t_type == task_type_pair) {
+#ifdef SWIFT_DEBUG_CHECKS
       atomic_inc(&ci->nr_tasks);
       atomic_inc(&cj->nr_tasks);
+#endif
 
       if (t_subtype == task_subtype_density) {
         engine_addlink(e, &ci->hydro.density, t);
@@ -2327,7 +2331,9 @@ void engine_count_and_link_tasks_mapper(void *map_data, int num_elements,
 
       /* Link sub-self tasks to cells. */
     } else if (t_type == task_type_sub_self) {
+#ifdef SWIFT_DEBUG_CHECKS
       atomic_inc(&ci->nr_tasks);
+#endif
 
       if (t_subtype == task_subtype_density) {
         engine_addlink(e, &ci->hydro.density, t);
@@ -2339,8 +2345,10 @@ void engine_count_and_link_tasks_mapper(void *map_data, int num_elements,
 
       /* Link sub-pair tasks to cells. */
     } else if (t_type == task_type_sub_pair) {
+#ifdef SWIFT_DEBUG_CHECKS
       atomic_inc(&ci->nr_tasks);
       atomic_inc(&cj->nr_tasks);
+#endif
 
       if (t_subtype == task_subtype_density) {
         engine_addlink(e, &ci->hydro.density, t);
diff --git a/src/engine_strays.c b/src/engine_strays.c
index 2ecd2576fcb0091534aaf64e83550eef9d82234e..2918cbeb521a19ee30f9abc6bf7c47ca32d57475 100644
--- a/src/engine_strays.c
+++ b/src/engine_strays.c
@@ -340,8 +340,6 @@ void engine_exchange_strays(
     count_sparts_in += e->proxies[k].nr_sparts_in;
     count_bparts_in += e->proxies[k].nr_bparts_in;
     count_sinks_in += e->proxies[k].nr_sinks_in;
-    message("Counting entering particles, k = %i, nr_proxies = %d", k,
-            e->nr_proxies);
   }
   if (e->verbose) {
     message(
diff --git a/src/engine_unskip.c b/src/engine_unskip.c
index 3e4140c20a85d5dfd1354d7f50c6e41a5d63d65e..473d5692e8f51743aa8c70e24b384e5ec1abc27b 100644
--- a/src/engine_unskip.c
+++ b/src/engine_unskip.c
@@ -423,12 +423,6 @@ void engine_unskip(struct engine *e) {
         memswap(&local_cells[k], &local_cells[num_active_cells], sizeof(int));
       num_active_cells += 1;
     }
-
-    /* Activate the top-level timestep exchange */
-#ifdef WITH_MPI
-    scheduler_activate_all_subtype(&e->sched, c->mpi.send, task_subtype_tend);
-    scheduler_activate_all_subtype(&e->sched, c->mpi.recv, task_subtype_tend);
-#endif
   }
 
   /* What kind of tasks do we have? */
diff --git a/src/feedback/GEAR/feedback_properties.h b/src/feedback/GEAR/feedback_properties.h
index 10d18896a9e58ca72d7c89236e0c593b0bf2ec22..080e3a042009241b162ca7bb38773d5e1a940785 100644
--- a/src/feedback/GEAR/feedback_properties.h
+++ b/src/feedback/GEAR/feedback_properties.h
@@ -147,7 +147,9 @@ __attribute__((always_inline)) INLINE static void feedback_props_init(
           "The metallicity threshold for the first stars is in mass fraction. "
           "It cannot be lower than 0.");
     }
-    message("Reading the stellar model for the first stars");
+    if (engine_rank == 0) {
+      message("Reading the stellar model for the first stars");
+    }
     parser_get_param_string(params, "GEARFeedback:yields_table_first_stars",
                             fp->stellar_model_first_stars.yields_table);
     stellar_evolution_props_init(&fp->stellar_model_first_stars, phys_const, us,
diff --git a/src/feedback/GEAR/initial_mass_function.c b/src/feedback/GEAR/initial_mass_function.c
index 7509ed28598cb5c41819606f1f469298ecc12c69..58cf13dcfe2213bc7eefdf30c2b1c1430b20e09f 100644
--- a/src/feedback/GEAR/initial_mass_function.c
+++ b/src/feedback/GEAR/initial_mass_function.c
@@ -65,6 +65,10 @@ float initial_mass_function_get_exponent(
 /** @brief Print the initial mass function */
 void initial_mass_function_print(const struct initial_mass_function *imf) {
 
+  if (engine_rank != 0) {
+    return;
+  }
+
   message("Number of parts: %i", imf->n_parts);
   message("Number of stars per mass units: %g", imf->N_tot);
   message("Mass interval: [%g, %g]", imf->mass_min, imf->mass_max);
diff --git a/src/feedback/GEAR/stellar_evolution.c b/src/feedback/GEAR/stellar_evolution.c
index 563870f0b1cb84cb3b247d27c4ce70721b862d9e..98b0436700d71ba58bf9d1c9a691fe1883049cd4 100644
--- a/src/feedback/GEAR/stellar_evolution.c
+++ b/src/feedback/GEAR/stellar_evolution.c
@@ -722,8 +722,11 @@ void stellar_evolution_props_init(struct stellar_model* sm,
 
   /* Convert from M_sun to internal units */
   sm->discrete_star_minimal_gravity_mass *= phys_const->const_solar_mass;
-  message("discrete_star_minimal_gravity_mass: (internal units)          %e",
-          sm->discrete_star_minimal_gravity_mass);
+
+  if (engine_rank == 0) {
+    message("discrete_star_minimal_gravity_mass: (internal units)          %e",
+            sm->discrete_star_minimal_gravity_mass);
+  }
 }
 
 /**
diff --git a/src/fof.c b/src/fof.c
index 6594b2b60efb8f7df858731f61adc60a6aa72dd3..4f4bc88999291af730da6104ce6689b37e939ae7 100644
--- a/src/fof.c
+++ b/src/fof.c
@@ -2986,6 +2986,9 @@ void fof_seed_black_holes(const struct fof_props *props,
       /* Save the ID */
       bp->id = p->id;
 
+      /* Save the tree depth */
+      bp->depth_h = p->depth_h;
+
 #ifdef SWIFT_DEBUG_CHECKS
       bp->ti_kick = p->ti_kick;
       bp->ti_drift = p->ti_drift;
diff --git a/src/hydro/AnarchyPU/hydro_part.h b/src/hydro/AnarchyPU/hydro_part.h
index ab9848bab728ee1722abecccf4feed86cc40b465..5b70117f2fca8f31f6a07715662841e90f9fbb16 100644
--- a/src/hydro/AnarchyPU/hydro_part.h
+++ b/src/hydro/AnarchyPU/hydro_part.h
@@ -239,6 +239,9 @@ struct part {
   /*! Time-step length */
   timebin_t time_bin;
 
+  /*! Tree-depth at which size / 2 <= h * gamma < size */
+  char depth_h;
+
   /*! Time-step limiter information */
   struct timestep_limiter_data limiter_data;
 
diff --git a/src/hydro/Gadget2/hydro_part.h b/src/hydro/Gadget2/hydro_part.h
index 62107dd43ff51b8b40f3370b86dd70c912271650..252e35fbcd0abe40020004a9ad3c4fb2f8c99931 100644
--- a/src/hydro/Gadget2/hydro_part.h
+++ b/src/hydro/Gadget2/hydro_part.h
@@ -199,6 +199,9 @@ struct part {
   /*! Time-step length */
   timebin_t time_bin;
 
+  /*! Tree-depth at which size / 2 <= h * gamma < size */
+  char depth_h;
+
   /*! Time-step limiter information */
   struct timestep_limiter_data limiter_data;
 
diff --git a/src/hydro/Gasoline/hydro_part.h b/src/hydro/Gasoline/hydro_part.h
index f9d300235eef1df25c08ee51e52755d2fb097684..9ee640c6b95b913100cbf8da1d7115fe9f5cae62 100644
--- a/src/hydro/Gasoline/hydro_part.h
+++ b/src/hydro/Gasoline/hydro_part.h
@@ -247,6 +247,9 @@ struct part {
   /*! Time-step length */
   timebin_t time_bin;
 
+  /*! Tree-depth at which size / 2 <= h * gamma < size */
+  char depth_h;
+
   /*! Time-step limiter information */
   struct timestep_limiter_data limiter_data;
 
diff --git a/src/hydro/Gizmo/MFM/hydro_part.h b/src/hydro/Gizmo/MFM/hydro_part.h
index f114dd2b9f9ed4ea81f9cfe86830c2849c0fe81f..b0cfc0dc410e5342d323551c701d1b41ebeb7fe1 100644
--- a/src/hydro/Gizmo/MFM/hydro_part.h
+++ b/src/hydro/Gizmo/MFM/hydro_part.h
@@ -177,6 +177,9 @@ struct part {
   /*! Time-step length */
   timebin_t time_bin;
 
+  /*! Tree-depth at which size / 2 <= h * gamma < size */
+  char depth_h;
+
   /*! Time-step limiter information */
   struct timestep_limiter_data limiter_data;
 
diff --git a/src/hydro/Gizmo/MFV/hydro_part.h b/src/hydro/Gizmo/MFV/hydro_part.h
index 7b0e22d1a860e009e61a79a732724aa7f0e8c0df..70b4857a59145c4c447b3a3c4a06b89db0acb697 100644
--- a/src/hydro/Gizmo/MFV/hydro_part.h
+++ b/src/hydro/Gizmo/MFV/hydro_part.h
@@ -176,6 +176,9 @@ struct part {
   /*! Time-step length */
   timebin_t time_bin;
 
+  /*! Tree-depth at which size / 2 <= h * gamma < size */
+  char depth_h;
+
   /*! Time-step limiter information */
   struct timestep_limiter_data limiter_data;
 
diff --git a/src/hydro/Gizmo/hydro_gradients.h b/src/hydro/Gizmo/hydro_gradients.h
index df6399fa25dd6ed533a4e105b1be0ebc1464319d..9ec2193caf9168fce62507919ca38ff117560fd0 100644
--- a/src/hydro/Gizmo/hydro_gradients.h
+++ b/src/hydro/Gizmo/hydro_gradients.h
@@ -57,8 +57,8 @@ __attribute__((always_inline)) INLINE static void hydro_gradients_init(
  * @param pj Particle j.
  */
 __attribute__((always_inline)) INLINE static void hydro_gradients_collect(
-    float r2, const float* dx, float hi, float hj, struct part* restrict pi,
-    struct part* restrict pj) {}
+    const float r2, const float dx[3], const float hi, const float hj,
+    struct part* restrict pi, struct part* restrict pj) {}
 
 /**
  * @brief Gradient calculations done during the neighbour loop: non-symmetric
@@ -72,7 +72,8 @@ __attribute__((always_inline)) INLINE static void hydro_gradients_collect(
  * @param pj Particle j.
  */
 __attribute__((always_inline)) INLINE static void
-hydro_gradients_nonsym_collect(float r2, const float* dx, float hi, float hj,
+hydro_gradients_nonsym_collect(const float r2, const float dx[3],
+                               const float hi, const float hj,
                                struct part* restrict pi,
                                struct part* restrict pj) {}
 
@@ -105,8 +106,9 @@ __attribute__((always_inline)) INLINE static float hydro_gradients_extrapolate(
  * gradients_none does nothing, since all gradients are zero -- are they?).
  */
 __attribute__((always_inline)) INLINE static void hydro_gradients_predict(
-    struct part* restrict pi, struct part* restrict pj, float hi, float hj,
-    const float* dx, float r, const float* xij_i, float* Wi, float* Wj) {
+    struct part* restrict pi, struct part* restrict pj, const float hi,
+    const float hj, const float* dx, float r, const float* xij_i, float* Wi,
+    float* Wj) {
 
   /* perform gradient reconstruction in space and time */
   /* Compute interface position (relative to pj, since we don't need the actual
diff --git a/src/hydro/Gizmo/hydro_gradients_gizmo.h b/src/hydro/Gizmo/hydro_gradients_gizmo.h
index 16e349a0912386c501a246412a743e9a67df39ed..3f2aeb174d13a30495ec2b1a88447fb8369ca727 100644
--- a/src/hydro/Gizmo/hydro_gradients_gizmo.h
+++ b/src/hydro/Gizmo/hydro_gradients_gizmo.h
@@ -48,8 +48,8 @@ __attribute__((always_inline)) INLINE static void hydro_gradients_init(
  * @param pj Particle j.
  */
 __attribute__((always_inline)) INLINE static void hydro_gradients_collect(
-    float r2, const float *dx, float hi, float hj, struct part *restrict pi,
-    struct part *restrict pj) {
+    const float r2, const float dx[3], const float hi, const float hj,
+    struct part *restrict pi, struct part *restrict pj) {
 
   /* Get r and 1/r. */
   const float r = sqrtf(r2);
@@ -176,7 +176,8 @@ __attribute__((always_inline)) INLINE static void hydro_gradients_collect(
  * @param pj Particle j.
  */
 __attribute__((always_inline)) INLINE static void
-hydro_gradients_nonsym_collect(float r2, const float *dx, float hi, float hj,
+hydro_gradients_nonsym_collect(const float r2, const float dx[3],
+                               const float hi, const float hj,
                                struct part *restrict pi,
                                struct part *restrict pj) {
 
diff --git a/src/hydro/Gizmo/hydro_gradients_sph.h b/src/hydro/Gizmo/hydro_gradients_sph.h
index ec4db739f6d4baa91e57a9201300d763109d7496..bb9fa22e9a67d841af6f34cacf8452580020c236 100644
--- a/src/hydro/Gizmo/hydro_gradients_sph.h
+++ b/src/hydro/Gizmo/hydro_gradients_sph.h
@@ -47,8 +47,8 @@ __attribute__((always_inline)) INLINE static void hydro_gradients_init(
  * @param pj Particle j.
  */
 __attribute__((always_inline)) INLINE static void hydro_gradients_collect(
-    float r2, const float *dx, float hi, float hj, struct part *restrict pi,
-    struct part *restrict pj) {
+    const float r2, const float dx[3], const float hi, const float hj,
+    struct part *restrict pi, struct part *restrict pj) {
 
   /* Get r and 1/r. */
   const float r = sqrtf(r2);
@@ -139,7 +139,8 @@ __attribute__((always_inline)) INLINE static void hydro_gradients_collect(
  * @param pj Particle j.
  */
 __attribute__((always_inline)) INLINE static void
-hydro_gradients_nonsym_collect(float r2, const float *dx, float hi, float hj,
+hydro_gradients_nonsym_collect(const float r2, const float dx[3],
+                               const float hi, const float hj,
                                struct part *restrict pi,
                                struct part *restrict pj) {
 
diff --git a/src/hydro/Minimal/hydro_part.h b/src/hydro/Minimal/hydro_part.h
index 1e3e239cc73e71c5173ae74a2bfdcdc507cde381..22e5f6f8c5751266997517715cdd3f1de1a32bcf 100644
--- a/src/hydro/Minimal/hydro_part.h
+++ b/src/hydro/Minimal/hydro_part.h
@@ -217,6 +217,9 @@ struct part {
   /*! Time-step length */
   timebin_t time_bin;
 
+  /*! Tree-depth at which size / 2 <= h * gamma < size */
+  char depth_h;
+
   /*! Time-step limiter information */
   struct timestep_limiter_data limiter_data;
 
diff --git a/src/hydro/None/hydro_part.h b/src/hydro/None/hydro_part.h
index 589c874e9716bb17c97d14da6d6e202ccffa6439..354aec0c86f26e6da2299cae84bb0ade2b817f93 100644
--- a/src/hydro/None/hydro_part.h
+++ b/src/hydro/None/hydro_part.h
@@ -179,6 +179,9 @@ struct part {
   /*! Time-step length */
   timebin_t time_bin;
 
+  /*! Tree-depth at which size / 2 <= h * gamma < size */
+  char depth_h;
+
   /*! Time-step limiter information */
   struct timestep_limiter_data limiter_data;
 
diff --git a/src/hydro/Phantom/hydro_part.h b/src/hydro/Phantom/hydro_part.h
index 419f0e6048a93238cf6104f0d62bd08dede41821..032198a5cd6cfeb0b5bef34670fdbca97d345ae0 100644
--- a/src/hydro/Phantom/hydro_part.h
+++ b/src/hydro/Phantom/hydro_part.h
@@ -237,6 +237,9 @@ struct part {
   /*! Time-step length */
   timebin_t time_bin;
 
+  /*! Tree-depth at which size / 2 <= h * gamma < size */
+  char depth_h;
+
   /*! Time-step limiter information */
   struct timestep_limiter_data limiter_data;
 
diff --git a/src/hydro/Planetary/hydro_iact.h b/src/hydro/Planetary/hydro_iact.h
index 0bf8895b6f5af3f9b53c6fb55c5c280605e99d4f..bb9357ad822e125d7555edb5db02b1b63c62f82b 100644
--- a/src/hydro/Planetary/hydro_iact.h
+++ b/src/hydro/Planetary/hydro_iact.h
@@ -122,6 +122,13 @@ __attribute__((always_inline)) INLINE static void runner_iact_density(
   pj->density.rot_v[0] += facj * curlvr[0];
   pj->density.rot_v[1] += facj * curlvr[1];
   pj->density.rot_v[2] += facj * curlvr[2];
+
+#ifdef SWIFT_HYDRO_DENSITY_CHECKS
+  pi->n_density += wi;
+  pj->n_density += wj;
+  pi->N_density++;
+  pj->N_density++;
+#endif
 }
 
 /**
@@ -187,6 +194,11 @@ __attribute__((always_inline)) INLINE static void runner_iact_nonsym_density(
   pi->density.rot_v[0] += faci * curlvr[0];
   pi->density.rot_v[1] += faci * curlvr[1];
   pi->density.rot_v[2] += faci * curlvr[2];
+
+#ifdef SWIFT_HYDRO_DENSITY_CHECKS
+  pi->n_density += wi;
+  pi->N_density++;
+#endif
 }
 
 /**
@@ -360,6 +372,13 @@ __attribute__((always_inline)) INLINE static void runner_iact_force(
   /* Update the signal velocity. */
   pi->force.v_sig = max(pi->force.v_sig, v_sig);
   pj->force.v_sig = max(pj->force.v_sig, v_sig);
+
+#ifdef SWIFT_HYDRO_DENSITY_CHECKS
+  pi->n_force += wi + wj;
+  pj->n_force += wi + wj;
+  pi->N_force++;
+  pj->N_force++;
+#endif
 }
 
 /**
@@ -483,6 +502,11 @@ __attribute__((always_inline)) INLINE static void runner_iact_nonsym_force(
 
   /* Update the signal velocity. */
   pi->force.v_sig = max(pi->force.v_sig, v_sig);
+
+#ifdef SWIFT_HYDRO_DENSITY_CHECKS
+  pi->n_force += wi + wj;
+  pi->N_force++;
+#endif
 }
 
 #endif /* SWIFT_PLANETARY_HYDRO_IACT_H */
diff --git a/src/hydro/Planetary/hydro_part.h b/src/hydro/Planetary/hydro_part.h
index 1d10aefdeb731e16cc6bbb495323a6a6a5eb8eab..4732cccaf716b31a170c258054ef94bdde0cc89a 100644
--- a/src/hydro/Planetary/hydro_part.h
+++ b/src/hydro/Planetary/hydro_part.h
@@ -219,6 +219,9 @@ struct part {
   /*! Time-step length */
   timebin_t time_bin;
 
+  /*! Tree-depth at which size / 2 <= h * gamma < size */
+  char depth_h;
+
   /*! Time-step limiter information */
   struct timestep_limiter_data limiter_data;
 
@@ -232,6 +235,54 @@ struct part {
 
 #endif
 
+#ifdef SWIFT_HYDRO_DENSITY_CHECKS
+
+  /* Integer number of neighbours in the density loop */
+  int N_density;
+
+  /* Exact integer number of neighbours in the density loop */
+  int N_density_exact;
+
+  /* Integer number of neighbours in the gradient loop */
+  int N_gradient;
+
+  /* Exact integer number of neighbours in the gradient loop */
+  int N_gradient_exact;
+
+  /* Integer number of neighbours in the force loop */
+  int N_force;
+
+  /* Exact integer number of neighbours in the force loop */
+  int N_force_exact;
+
+  /*! Exact value of the density field obtained via brute-force loop */
+  float rho_exact;
+
+  /*! Weighted numer of neighbours in the density loop */
+  float n_density;
+
+  /*! Exact value of the weighted numer of neighbours in the density loop */
+  float n_density_exact;
+
+  /*! Weighted numer of neighbours in the gradient loop */
+  float n_gradient;
+
+  /*! Exact value of the weighted numer of neighbours in the gradient loop */
+  float n_gradient_exact;
+
+  /*! Weighted numer of neighbours in the force loop */
+  float n_force;
+
+  /*! Exact value of the weighted numer of neighbours in the force loop */
+  float n_force_exact;
+
+  /*! Has this particle interacted with any unhibited neighbour? */
+  char inhibited_exact;
+
+  /*! Has this particle been woken up by the limiter? */
+  char limited_part;
+#endif
+
 #ifdef PLANETARY_FIXED_ENTROPY
   /* Fixed specific entropy */
   float s_fixed;
diff --git a/src/hydro/PressureEnergy/hydro_part.h b/src/hydro/PressureEnergy/hydro_part.h
index a642baf82473527a29965869547324cdecda77ff..eedf2fe66684ca7f06e848d2d7a744cac719fd26 100644
--- a/src/hydro/PressureEnergy/hydro_part.h
+++ b/src/hydro/PressureEnergy/hydro_part.h
@@ -224,6 +224,9 @@ struct part {
   /*! Time-step length */
   timebin_t time_bin;
 
+  /*! Tree-depth at which size / 2 <= h * gamma < size */
+  char depth_h;
+
   /*! Time-step limiter information */
   struct timestep_limiter_data limiter_data;
 
diff --git a/src/hydro/PressureEnergyMorrisMonaghanAV/hydro_part.h b/src/hydro/PressureEnergyMorrisMonaghanAV/hydro_part.h
index 51df80140e3359d32899c5e6a0d1f696c7ab311f..c28d74c64cb05b42f65f1fa5c71308937f301982 100644
--- a/src/hydro/PressureEnergyMorrisMonaghanAV/hydro_part.h
+++ b/src/hydro/PressureEnergyMorrisMonaghanAV/hydro_part.h
@@ -221,6 +221,9 @@ struct part {
   /*! Time-step length */
   timebin_t time_bin;
 
+  /*! Tree-depth at which size / 2 <= h * gamma < size */
+  char depth_h;
+
   /*! Time-step limiter information */
   struct timestep_limiter_data limiter_data;
 
diff --git a/src/hydro/PressureEntropy/hydro_part.h b/src/hydro/PressureEntropy/hydro_part.h
index 55de43084a1b79d07a5595cec74e35f930327996..c0ade7783444bff93c47ac55d06761a929ee8e96 100644
--- a/src/hydro/PressureEntropy/hydro_part.h
+++ b/src/hydro/PressureEntropy/hydro_part.h
@@ -197,6 +197,9 @@ struct part {
   /*! Time-step length */
   timebin_t time_bin;
 
+  /*! Tree-depth at which size / 2 <= h * gamma < size */
+  char depth_h;
+
   /*! Time-step limiter information */
   struct timestep_limiter_data limiter_data;
 
diff --git a/src/hydro/SPHENIX/hydro_part.h b/src/hydro/SPHENIX/hydro_part.h
index e6e4fafe5f45a1a11a338cc4d1544b91d7b0a3a5..1365357cbd4c47cfa1deb136ec064552f085c50d 100644
--- a/src/hydro/SPHENIX/hydro_part.h
+++ b/src/hydro/SPHENIX/hydro_part.h
@@ -247,6 +247,9 @@ struct part {
   /*! RT sub-cycling time stepping data */
   struct rt_timestepping_data rt_time_data;
 
+  /*! Tree-depth at which size / 2 <= h * gamma < size */
+  char depth_h;
+
   /*! Time-step length */
   timebin_t time_bin;
 
diff --git a/src/hydro/Shadowswift/hydro_part.h b/src/hydro/Shadowswift/hydro_part.h
index a9b000a2b9c48475618e18381c1b9ab98d297d61..7546230a670e08da8b5809cebcb0675451c842f4 100644
--- a/src/hydro/Shadowswift/hydro_part.h
+++ b/src/hydro/Shadowswift/hydro_part.h
@@ -179,6 +179,9 @@ struct part {
   /*! Time-step length */
   timebin_t time_bin;
 
+  /*! Tree-depth at which size / 2 <= h * gamma < size */
+  char depth_h;
+
   /*! Time-step limiter information */
   struct timestep_limiter_data limiter_data;
 
diff --git a/src/part.h b/src/part.h
index c0e87bfde7c6525b7cdc05974d56fa4bb5be5bd9..bb574651b5a1fdb2005beeeacdc00208adb09f96 100644
--- a/src/part.h
+++ b/src/part.h
@@ -132,6 +132,8 @@ struct threadpool;
 /* Import the right sink particle definition */
 #if defined(SINK_NONE)
 #include "./sink/Default/sink_part.h"
+#elif defined(SINK_BASIC)
+#include "./sink/Basic/sink_part.h"
 #elif defined(SINK_GEAR)
 #include "./sink/GEAR/sink_part.h"
 #else
diff --git a/src/potential/MWPotential2014/potential.h b/src/potential/MWPotential2014/potential.h
index 84b20cb371ddedf5b6e5c3165b5741454667e904..588f1d309bd418f3de0c4073e8fbd17afa427685 100644
--- a/src/potential/MWPotential2014/potential.h
+++ b/src/potential/MWPotential2014/potential.h
@@ -368,7 +368,7 @@ static INLINE void potential_init_backend(
   const double Mdisk_Msun_default = 6.8e10;                /* M_sun  */
   const double Rdisk_kpc_default = 3.0;                    /* kpc  */
   const double Zdisk_kpc_default = 0.280;                  /* kpc  */
-  const double amplitude_default = 1.0;                    /* no unit  */
+  const double amplitude_Msun_per_kpc3_default = 1e10;     /* M_sun/kpc^3  */
   const double r_1_kpc_default = 1.0;                      /* kpc  */
   const double alpha_default = 1.8;                        /* no unit  */
   const double r_c_kpc_default = 1.9;                      /* kpc  */
@@ -404,7 +404,8 @@ static INLINE void potential_init_backend(
   potential->Zdisk = parser_get_opt_param_double(
       parameter_file, "MWPotential2014Potential:Zdisk_kpc", Zdisk_kpc_default);
   potential->amplitude = parser_get_opt_param_double(
-      parameter_file, "MWPotential2014Potential:amplitude", amplitude_default);
+      parameter_file, "MWPotential2014Potential:amplitude_Msun_per_kpc3",
+      amplitude_Msun_per_kpc3_default);
   potential->r_1 = parser_get_opt_param_double(
       parameter_file, "MWPotential2014Potential:r_1_kpc", r_1_kpc_default);
   potential->alpha = parser_get_opt_param_double(
@@ -417,13 +418,15 @@ static INLINE void potential_init_backend(
 
   /* Convert to internal system of units by using the
    * physical constants defined in this system */
+  const double kpc = 1000. * phys_const->const_parsec;
   potential->M_200 *= phys_const->const_solar_mass;
   potential->H *= phys_const->const_reduced_hubble;
   potential->Mdisk *= phys_const->const_solar_mass;
-  potential->Rdisk *= 1000. * phys_const->const_parsec;
-  potential->Zdisk *= 1000. * phys_const->const_parsec;
-  potential->r_1 *= 1000. * phys_const->const_parsec;
-  potential->r_c *= 1000. * phys_const->const_parsec;
+  potential->Rdisk *= kpc;
+  potential->Zdisk *= kpc;
+  potential->r_1 *= kpc;
+  potential->r_c *= kpc;
+  potential->amplitude *= phys_const->const_solar_mass / (kpc * kpc * kpc);
 
   /* Compute rho_c */
   const double rho_c = 3.0 * potential->H * potential->H /
diff --git a/src/proxy.c b/src/proxy.c
index 3830162ada2135d28c68b3763661a18e17bcbab0..f8d551c9ceb4a21f75bcbda6298871c8a53b9ab3 100644
--- a/src/proxy.c
+++ b/src/proxy.c
@@ -381,8 +381,8 @@ void proxy_cells_exchange_first(struct proxy *p) {
                   p->nodeID * proxy_tag_shift + proxy_tag_count, MPI_COMM_WORLD,
                   &p->req_cells_count_in);
   if (err != MPI_SUCCESS) mpi_error(err, "Failed to irecv nr of pcells.");
-    // message( "irecv pcells count on node %i from node %i." , p->mynodeID ,
-    // p->nodeID ); fflush(stdout);
+  // message( "irecv pcells count on node %i from node %i." , p->mynodeID ,
+  // p->nodeID ); fflush(stdout);
 
 #else
   error("SWIFT was not compiled with MPI support.");
@@ -415,8 +415,8 @@ void proxy_cells_exchange_second(struct proxy *p) {
                       MPI_COMM_WORLD, &p->req_cells_in);
 
   if (err != MPI_SUCCESS) mpi_error(err, "Failed to irecv part data.");
-    // message( "irecv pcells (%i) on node %i from node %i." , p->size_pcells_in
-    // , p->mynodeID , p->nodeID ); fflush(stdout);
+  // message( "irecv pcells (%i) on node %i from node %i." , p->size_pcells_in
+  // , p->mynodeID , p->nodeID ); fflush(stdout);
 
 #else
   error("SWIFT was not compiled with MPI support.");
@@ -796,7 +796,7 @@ void proxy_parts_exchange_first(struct proxy *p) {
     for (int k = 0; k < p->nr_sinks_out; k++)
       message("sending sinks %lli, x=[%.3e %.3e %.3e], h=%.3e, to node %i.",
               p->sinks_out[k].id, p->sinks_out[k].x[0], p->sinks_out[k].x[1],
-              p->sinks_out[k].x[2], p->sinks_out[k].r_cut, p->nodeID);
+              p->sinks_out[k].x[2], p->sinks_out[k].h, p->nodeID);
 #endif /* SWIFT_DEBUG_CHECKS */
   }
 
@@ -953,7 +953,7 @@ void proxy_parts_exchange_second(struct proxy *p) {
     for (int k = 0; k < p->nr_sinks_in; k++)
       message("receiving sinks %lli, x=[%.3e %.3e %.3e], h=%.3e, from node %i.",
               p->sinks_in[k].id, p->sinks_in[k].x[0], p->sinks_in[k].x[1],
-              p->sinks_in[k].x[2], p->sinks_in[k].r_cut, p->nodeID);
+              p->sinks_in[k].x[2], p->sinks_in[k].h, p->nodeID);
 #endif /* SWIFT_DEBUG_CHECKS */
   }
 
diff --git a/src/random.h b/src/random.h
index 6ec0b6b7a2e3ed8f6d6936c4f943697a9bfd8f4c..bcd28ad473da3a11bd1bdb9c3f93cce781f425af 100644
--- a/src/random.h
+++ b/src/random.h
@@ -62,6 +62,7 @@ enum random_number_type {
   random_number_BH_reposition = 59969537LL,
   random_number_BH_spin = 193877777LL,
   random_number_BH_kick = 303595777LL,
+  random_number_sink_swallow = 7337737777LL,
   random_number_snapshot_sampling = 6561001LL,
   random_number_stellar_winds = 5947309451LL,
   random_number_HII_regions = 8134165677LL,
diff --git a/src/rays.h b/src/rays.h
index cf1179873e5702dbf28007557226d70dd8294bee..c5b9245dce4bb85ec7969057a8d46cd1e9a954d5 100644
--- a/src/rays.h
+++ b/src/rays.h
@@ -132,7 +132,7 @@ __attribute__((always_inline)) INLINE static float ray_arclength(
  * @param v Gas particle velocity
  */
 __attribute__((always_inline)) INLINE static void ray_minimise_arclength(
-    const float *dx, const float r, struct ray_data *ray,
+    const float dx[3], const float r, struct ray_data *ray,
     const ray_feedback_type ray_type, const long long gas_part_id,
     const double rand_theta_gen, const double rand_phi_gen, const float m,
     struct ray_data_extra *ray_ext, const float *v) {
diff --git a/src/restart.c b/src/restart.c
index ed9d346457c60bbec81e85d66e800018d16b0e7f..e3511bc48828b7fa952441213d169031628d578a 100644
--- a/src/restart.c
+++ b/src/restart.c
@@ -191,19 +191,21 @@ void restart_read(struct engine *e, const char *filename) {
   if (stream == NULL)
     error("Failed to open restart file: %s (%s)", filename, strerror(errno));
 
-  /* Get our version and signature back. These should match. */
-  char signature[strlen(SWIFT_RESTART_SIGNATURE) + 1];
-  int len = strlen(SWIFT_RESTART_SIGNATURE);
-  restart_read_blocks(signature, len, 1, stream, NULL, "SWIFT signature");
-  signature[len] = '\0';
-  if (strncmp(signature, SWIFT_RESTART_SIGNATURE, len) != 0)
+  /* Get our version and signature back. These should match.
+   * Use static int here to avoid compiler warnings about gnu-extensions
+   * of folding a variable length array to constant array. */
+  const int sig_len = strlen(SWIFT_RESTART_SIGNATURE);
+  char signature[sig_len + 1];
+  restart_read_blocks(signature, sig_len, 1, stream, NULL, "SWIFT signature");
+  signature[sig_len] = '\0';
+  if (strncmp(signature, SWIFT_RESTART_SIGNATURE, sig_len) != 0)
     error(
         "Do not recognise this as a SWIFT restart file, found '%s' "
         "expected '%s'",
         signature, SWIFT_RESTART_SIGNATURE);
 
   char version[FNAMELEN];
-  len = strlen(package_version());
+  int len = strlen(package_version());
   restart_read_blocks(version, len, 1, stream, NULL, "SWIFT version");
   version[len] = '\0';
 
diff --git a/src/rt/GEAR/rt_gradients.h b/src/rt/GEAR/rt_gradients.h
index 4b2032aec05fad98ca1a1a80a054aecda1d2b7a7..6bef2520d79a1910896d373079a604ae5be39e2b 100644
--- a/src/rt/GEAR/rt_gradients.h
+++ b/src/rt/GEAR/rt_gradients.h
@@ -138,8 +138,8 @@ __attribute__((always_inline)) INLINE static void rt_finalise_gradient_part(
  * @param pj Particle j.
  */
 __attribute__((always_inline)) INLINE static void rt_gradients_collect(
-    float r2, const float dx[3], float hi, float hj, struct part *restrict pi,
-    struct part *restrict pj) {
+    const float r2, const float dx[3], const float hi, const float hj,
+    struct part *restrict pi, struct part *restrict pj) {
 
 #ifdef SWIFT_RT_DEBUG_CHECKS
   rt_debug_sequence_check(pi, 2, __func__);
@@ -277,8 +277,8 @@ __attribute__((always_inline)) INLINE static void rt_gradients_collect(
  * @param pj Particle j.
  */
 __attribute__((always_inline)) INLINE static void rt_gradients_nonsym_collect(
-    float r2, const float dx[3], float hi, float hj, struct part *restrict pi,
-    struct part *restrict pj) {
+    const float r2, const float dx[3], const float hi, const float hj,
+    struct part *restrict pi, struct part *restrict pj) {
 
 #ifdef SWIFT_RT_DEBUG_CHECKS
   rt_debug_sequence_check(pi, 2, __func__);
@@ -386,7 +386,7 @@ __attribute__((always_inline)) INLINE static float rt_gradients_extrapolate(
  */
 __attribute__((always_inline)) INLINE static void rt_gradients_predict(
     const struct part *restrict pi, const struct part *restrict pj, float Ui[4],
-    float Uj[4], int group, const float *dx, const float r,
+    float Uj[4], int group, const float dx[3], const float r,
     const float xij_i[3]) {
 
   rt_part_get_radiation_state_vector(pi, group, Ui);
diff --git a/src/rt/GEAR/rt_iact.h b/src/rt/GEAR/rt_iact.h
index 21d2be8f5b46339a6aeb4f34d0aeefca7287391f..9fce2eeb39b4b10f6985cb8ff306f08064e807d0 100644
--- a/src/rt/GEAR/rt_iact.h
+++ b/src/rt/GEAR/rt_iact.h
@@ -45,7 +45,7 @@
  */
 
 __attribute__((always_inline)) INLINE static void
-runner_iact_nonsym_rt_injection_prep(const float r2, const float *dx,
+runner_iact_nonsym_rt_injection_prep(const float r2, const float dx[3],
                                      const float hi, const float hj,
                                      struct spart *si, const struct part *pj,
                                      const struct cosmology *cosmo,
@@ -97,9 +97,9 @@ runner_iact_nonsym_rt_injection_prep(const float r2, const float *dx,
  * @param rt_props Properties of the RT scheme.
  */
 __attribute__((always_inline)) INLINE static void runner_iact_rt_inject(
-    const float r2, float *dx, const float hi, const float hj,
-    struct spart *restrict si, struct part *restrict pj, float a, float H,
-    const struct rt_props *rt_props) {
+    const float r2, float dx[3], const float hi, const float hj,
+    struct spart *restrict si, struct part *restrict pj, const float a,
+    const float H, const struct rt_props *rt_props) {
 
   /* If the star doesn't have any neighbours, we
    * have nothing to do here. */
@@ -207,8 +207,9 @@ __attribute__((always_inline)) INLINE static void runner_iact_rt_inject(
  * @param mode 0 if non-symmetric interaction, 1 if symmetric
  */
 __attribute__((always_inline)) INLINE static void runner_iact_rt_flux_common(
-    float r2, const float *dx, float hi, float hj, struct part *restrict pi,
-    struct part *restrict pj, float a, float H, int mode) {
+    const float r2, const float dx[3], const float hi, const float hj,
+    struct part *restrict pi, struct part *restrict pj, const float a,
+    const float H, int mode) {
 
 #ifdef SWIFT_RT_DEBUG_CHECKS
   const char *func_name = (mode == 1) ? "sym flux iact" : "nonsym flux iact";
@@ -388,8 +389,9 @@ __attribute__((always_inline)) INLINE static void runner_iact_rt_flux_common(
  * @param H Current Hubble parameter.
  */
 __attribute__((always_inline)) INLINE static void runner_iact_rt_transport(
-    float r2, const float *dx, float hi, float hj, struct part *restrict pi,
-    struct part *restrict pj, float a, float H) {
+    const float r2, const float dx[3], const float hi, const float hj,
+    struct part *restrict pi, struct part *restrict pj, const float a,
+    const float H) {
 
   runner_iact_rt_flux_common(r2, dx, hi, hj, pi, pj, a, H, 1);
 }
@@ -411,9 +413,11 @@ __attribute__((always_inline)) INLINE static void runner_iact_rt_transport(
  * @param H Current Hubble parameter.
  */
 __attribute__((always_inline)) INLINE static void
-runner_iact_nonsym_rt_transport(float r2, const float *dx, float hi, float hj,
+runner_iact_nonsym_rt_transport(const float r2, const float dx[3],
+                                const float hi, const float hj,
                                 struct part *restrict pi,
-                                struct part *restrict pj, float a, float H) {
+                                struct part *restrict pj, const float a,
+                                const float H) {
 
   runner_iact_rt_flux_common(r2, dx, hi, hj, pi, pj, a, H, 0);
 }
@@ -435,8 +439,9 @@ runner_iact_nonsym_rt_transport(float r2, const float *dx, float hi, float hj,
  * @param H Current Hubble parameter.
  */
 __attribute__((always_inline)) INLINE static void runner_iact_rt_gradient(
-    float r2, const float *dx, float hi, float hj, struct part *restrict pi,
-    struct part *restrict pj, float a, float H) {
+    const float r2, const float dx[3], const float hi, const float hj,
+    struct part *restrict pi, struct part *restrict pj, const float a,
+    const float H) {
 
   rt_gradients_collect(r2, dx, hi, hj, pi, pj);
 }
@@ -459,9 +464,11 @@ __attribute__((always_inline)) INLINE static void runner_iact_rt_gradient(
  * @param H Current Hubble parameter.
  */
 __attribute__((always_inline)) INLINE static void
-runner_iact_nonsym_rt_gradient(float r2, const float *dx, float hi, float hj,
+runner_iact_nonsym_rt_gradient(const float r2, const float dx[3],
+                               const float hi, const float hj,
                                struct part *restrict pi,
-                               struct part *restrict pj, float a, float H) {
+                               struct part *restrict pj, const float a,
+                               const float H) {
 
   rt_gradients_nonsym_collect(r2, dx, hi, hj, pi, pj);
 }
diff --git a/src/rt/SPHM1RT/rt_iact.h b/src/rt/SPHM1RT/rt_iact.h
index 23242980cc7cf653c225f36108ac86606d3b7977..bead428d752b2cd83c4e462b48c2910ad23abc20 100644
--- a/src/rt/SPHM1RT/rt_iact.h
+++ b/src/rt/SPHM1RT/rt_iact.h
@@ -43,7 +43,7 @@
  */
 
 __attribute__((always_inline)) INLINE static void
-runner_iact_nonsym_rt_injection_prep(const float r2, const float *dx,
+runner_iact_nonsym_rt_injection_prep(const float r2, const float dx[3],
                                      const float hi, const float hj,
                                      struct spart *si, const struct part *pj,
                                      const struct cosmology *cosmo,
@@ -100,9 +100,9 @@ runner_iact_nonsym_rt_injection_prep(const float r2, const float *dx,
  * @param rt_props Properties of the RT scheme.
  */
 __attribute__((always_inline)) INLINE static void runner_iact_rt_inject(
-    const float r2, float *dx, const float hi, const float hj,
-    struct spart *restrict si, struct part *restrict pj, float a, float H,
-    const struct rt_props *rt_props) {
+    const float r2, float dx[3], const float hi, const float hj,
+    struct spart *restrict si, struct part *restrict pj, const float a,
+    const float H, const struct rt_props *rt_props) {
 
   /* If the star doesn't have any neighbours, we
    * have nothing to do here. */
@@ -189,10 +189,11 @@ __attribute__((always_inline)) INLINE static void runner_iact_rt_inject(
  *
  */
 __attribute__((always_inline)) INLINE static void
-radiation_gradient_loop_function(float r2, const float *dx, float hi, float hj,
+radiation_gradient_loop_function(const float r2, const float dx[3],
+                                 const float hi, const float hj,
                                  struct part *restrict pi,
-                                 struct part *restrict pj, float a, float H,
-                                 int mode) {
+                                 struct part *restrict pj, const float a,
+                                 const float H, int mode) {
 
   struct rt_part_data *rpi = &pi->rt_data;
   struct rt_part_data *rpj = &pj->rt_data;
@@ -311,8 +312,9 @@ radiation_gradient_loop_function(float r2, const float *dx, float hi, float hj,
  * @param H Current Hubble parameter.
  */
 __attribute__((always_inline)) INLINE static void runner_iact_rt_gradient(
-    float r2, const float *dx, float hi, float hj, struct part *restrict pi,
-    struct part *restrict pj, float a, float H) {
+    const float r2, const float dx[3], const float hi, const float hj,
+    struct part *restrict pi, struct part *restrict pj, const float a,
+    const float H) {
   radiation_gradient_loop_function(r2, dx, hi, hj, pi, pj, a, H, 1);
 }
 
@@ -331,9 +333,11 @@ __attribute__((always_inline)) INLINE static void runner_iact_rt_gradient(
  * @param H Current Hubble parameter.
  */
 __attribute__((always_inline)) INLINE static void
-runner_iact_nonsym_rt_gradient(float r2, const float *dx, float hi, float hj,
+runner_iact_nonsym_rt_gradient(const float r2, const float dx[3],
+                               const float hi, const float hj,
                                struct part *restrict pi,
-                               struct part *restrict pj, float a, float H) {
+                               struct part *restrict pj, const float a,
+                               const float H) {
   radiation_gradient_loop_function(r2, dx, hi, hj, pi, pj, a, H, 0);
 }
 
@@ -353,8 +357,9 @@ runner_iact_nonsym_rt_gradient(float r2, const float *dx, float hi, float hj,
  *
  */
 __attribute__((always_inline)) INLINE static void radiation_force_loop_function(
-    float r2, const float *dx, float hi, float hj, struct part *restrict pi,
-    struct part *restrict pj, float a, float H, int mode) {
+    const float r2, const float dx[3], const float hi, const float hj,
+    struct part *restrict pi, struct part *restrict pj, const float a,
+    const float H, int mode) {
 
   struct rt_part_data *rpi = &pi->rt_data;
   struct rt_part_data *rpj = &pj->rt_data;
@@ -736,8 +741,9 @@ __attribute__((always_inline)) INLINE static void radiation_force_loop_function(
  * @param H Current Hubble parameter.
  */
 __attribute__((always_inline)) INLINE static void runner_iact_rt_transport(
-    float r2, const float *dx, float hi, float hj, struct part *restrict pi,
-    struct part *restrict pj, float a, float H) {
+    const float r2, const float dx[3], const float hi, const float hj,
+    struct part *restrict pi, struct part *restrict pj, const float a,
+    const float H) {
 
   radiation_force_loop_function(r2, dx, hi, hj, pi, pj, a, H, 1);
 }
@@ -757,9 +763,11 @@ __attribute__((always_inline)) INLINE static void runner_iact_rt_transport(
  * @param H Current Hubble parameter.
  */
 __attribute__((always_inline)) INLINE static void
-runner_iact_nonsym_rt_transport(float r2, const float *dx, float hi, float hj,
+runner_iact_nonsym_rt_transport(const float r2, const float dx[3],
+                                const float hi, const float hj,
                                 struct part *restrict pi,
-                                struct part *restrict pj, float a, float H) {
+                                struct part *restrict pj, const float a,
+                                const float H) {
 
   radiation_force_loop_function(r2, dx, hi, hj, pi, pj, a, H, 0);
 }
diff --git a/src/rt/debug/rt_gradients.h b/src/rt/debug/rt_gradients.h
index 3d2dfc8b0cd238e100872df5d1827e928cd3034c..bf80fbfc378d0c92a0d02667995984dc2ee10d0e 100644
--- a/src/rt/debug/rt_gradients.h
+++ b/src/rt/debug/rt_gradients.h
@@ -37,8 +37,8 @@
  * @param pj Particle j.
  */
 __attribute__((always_inline)) INLINE static void rt_gradients_collect(
-    float r2, const float dx[3], float hi, float hj, struct part *restrict pi,
-    struct part *restrict pj) {
+    const float r2, const float dx[3], const float hi, const float hj,
+    struct part *restrict pi, struct part *restrict pj) {
 
   rt_debug_sequence_check(pi, 2, __func__);
   rt_debug_sequence_check(pj, 2, __func__);
@@ -58,8 +58,8 @@ __attribute__((always_inline)) INLINE static void rt_gradients_collect(
  * @param pj Particle j.
  */
 __attribute__((always_inline)) INLINE static void rt_gradients_nonsym_collect(
-    float r2, const float dx[3], float hi, float hj, struct part *restrict pi,
-    struct part *restrict pj) {
+    const float r2, const float dx[3], const float hi, const float hj,
+    struct part *restrict pi, struct part *restrict pj) {
 
   rt_debug_sequence_check(pi, 2, __func__);
   pi->rt_data.debug_calls_iact_gradient_interaction += 1;
diff --git a/src/rt/debug/rt_iact.h b/src/rt/debug/rt_iact.h
index 2eb590dd43281affb0c3aece562a7d5f71948376..d4a4f0d08c8fe004259035c25dbe7138a30ede54 100644
--- a/src/rt/debug/rt_iact.h
+++ b/src/rt/debug/rt_iact.h
@@ -43,7 +43,7 @@
  */
 
 __attribute__((always_inline)) INLINE static void
-runner_iact_nonsym_rt_injection_prep(const float r2, const float *dx,
+runner_iact_nonsym_rt_injection_prep(const float r2, const float dx[3],
                                      const float hi, const float hj,
                                      struct spart *si, const struct part *pj,
                                      const struct cosmology *cosmo,
@@ -66,9 +66,9 @@ runner_iact_nonsym_rt_injection_prep(const float r2, const float *dx,
  * @param rt_props Properties of the RT scheme.
  */
 __attribute__((always_inline)) INLINE static void runner_iact_rt_inject(
-    const float r2, float *dx, const float hi, const float hj,
-    struct spart *restrict si, struct part *restrict pj, float a, float H,
-    const struct rt_props *rt_props) {
+    const float r2, float dx[3], const float hi, const float hj,
+    struct spart *restrict si, struct part *restrict pj, const float a,
+    const float H, const struct rt_props *rt_props) {
 
   /* If the star doesn't have any neighbours, we
    * have nothing to do here. */
@@ -107,8 +107,9 @@ __attribute__((always_inline)) INLINE static void runner_iact_rt_inject(
  * @param mode 0 if non-symmetric interaction, 1 if symmetric
  */
 __attribute__((always_inline)) INLINE static void runner_iact_rt_flux_common(
-    float r2, const float *dx, float hi, float hj, struct part *restrict pi,
-    struct part *restrict pj, float a, float H, int mode) {
+    const float r2, const float dx[3], const float hi, const float hj,
+    struct part *restrict pi, struct part *restrict pj, const float a,
+    const float H, int mode) {
 
   const char *func_name = (mode == 1) ? "sym flux iact" : "nonsym flux iact";
   rt_debug_sequence_check(pi, 3, func_name);
@@ -136,8 +137,9 @@ __attribute__((always_inline)) INLINE static void runner_iact_rt_flux_common(
  * @param H Current Hubble parameter.
  */
 __attribute__((always_inline)) INLINE static void runner_iact_rt_transport(
-    float r2, const float *dx, float hi, float hj, struct part *restrict pi,
-    struct part *restrict pj, float a, float H) {
+    const float r2, const float dx[3], const float hi, const float hj,
+    struct part *restrict pi, struct part *restrict pj, const float a,
+    const float H) {
 
   runner_iact_rt_flux_common(r2, dx, hi, hj, pi, pj, a, H, 1);
 }
@@ -159,9 +161,11 @@ __attribute__((always_inline)) INLINE static void runner_iact_rt_transport(
  * @param H Current Hubble parameter.
  */
 __attribute__((always_inline)) INLINE static void
-runner_iact_nonsym_rt_transport(float r2, const float *dx, float hi, float hj,
+runner_iact_nonsym_rt_transport(const float r2, const float dx[3],
+                                const float hi, const float hj,
                                 struct part *restrict pi,
-                                struct part *restrict pj, float a, float H) {
+                                struct part *restrict pj, const float a,
+                                const float H) {
 
   runner_iact_rt_flux_common(r2, dx, hi, hj, pi, pj, a, H, 0);
 }
@@ -183,8 +187,9 @@ runner_iact_nonsym_rt_transport(float r2, const float *dx, float hi, float hj,
  * @param H Current Hubble parameter.
  */
 __attribute__((always_inline)) INLINE static void runner_iact_rt_gradient(
-    float r2, const float *dx, float hi, float hj, struct part *restrict pi,
-    struct part *restrict pj, float a, float H) {
+    const float r2, const float dx[3], const float hi, const float hj,
+    struct part *restrict pi, struct part *restrict pj, const float a,
+    const float H) {
 
   rt_gradients_collect(r2, dx, hi, hj, pi, pj);
 }
@@ -207,9 +212,11 @@ __attribute__((always_inline)) INLINE static void runner_iact_rt_gradient(
  * @param H Current Hubble parameter.
  */
 __attribute__((always_inline)) INLINE static void
-runner_iact_nonsym_rt_gradient(float r2, const float *dx, float hi, float hj,
+runner_iact_nonsym_rt_gradient(const float r2, const float dx[3],
+                               const float hi, const float hj,
                                struct part *restrict pi,
-                               struct part *restrict pj, float a, float H) {
+                               struct part *restrict pj, const float a,
+                               const float H) {
 
   rt_gradients_nonsym_collect(r2, dx, hi, hj, pi, pj);
 }
diff --git a/src/rt/none/rt_iact.h b/src/rt/none/rt_iact.h
index f8a3d85447ae7b463e9f7382d797af1037bf2cb5..cee3bbc145c5f627bdfb7f7690dfc5e7127540dc 100644
--- a/src/rt/none/rt_iact.h
+++ b/src/rt/none/rt_iact.h
@@ -40,7 +40,7 @@
  */
 
 __attribute__((always_inline)) INLINE static void
-runner_iact_nonsym_rt_injection_prep(const float r2, const float *dx,
+runner_iact_nonsym_rt_injection_prep(const float r2, const float dx[3],
                                      const float hi, const float hj,
                                      struct spart *si, const struct part *pj,
                                      const struct cosmology *cosmo,
@@ -60,9 +60,9 @@ runner_iact_nonsym_rt_injection_prep(const float r2, const float *dx,
  * @param rt_props Properties of the RT scheme.
  */
 __attribute__((always_inline)) INLINE static void runner_iact_rt_inject(
-    const float r2, float *dx, const float hi, const float hj,
-    struct spart *restrict si, struct part *restrict pj, float a, float H,
-    const struct rt_props *rt_props) {}
+    const float r2, const float dx[3], const float hi, const float hj,
+    struct spart *restrict si, struct part *restrict pj, const float a,
+    const float H, const struct rt_props *rt_props) {}
 
 /**
  * @brief Flux calculation between particle i and particle j
@@ -78,8 +78,9 @@ __attribute__((always_inline)) INLINE static void runner_iact_rt_inject(
  * @param H Current Hubble parameter.
  */
 __attribute__((always_inline)) INLINE static void runner_iact_rt_transport(
-    float r2, const float *dx, float hi, float hj, struct part *restrict pi,
-    struct part *restrict pj, float a, float H) {}
+    const float r2, const float dx[3], const float hi, const float hj,
+    struct part *restrict pi, struct part *restrict pj, const float a,
+    const float H) {}
 
 /**
  * @brief Flux calculation between particle i and particle j: non-symmetric
@@ -96,9 +97,11 @@ __attribute__((always_inline)) INLINE static void runner_iact_rt_transport(
  * @param H Current Hubble parameter.
  */
 __attribute__((always_inline)) INLINE static void
-runner_iact_nonsym_rt_transport(float r2, const float *dx, float hi, float hj,
+runner_iact_nonsym_rt_transport(const float r2, const float dx[3],
+                                const float hi, const float hj,
                                 struct part *restrict pi,
-                                struct part *restrict pj, float a, float H) {}
+                                struct part *restrict pj, const float a,
+                                const float H) {}
 
 /**
  * @brief Calculate the gradient interaction between particle i and particle j
@@ -114,8 +117,9 @@ runner_iact_nonsym_rt_transport(float r2, const float *dx, float hi, float hj,
  * @param H Current Hubble parameter.
  */
 __attribute__((always_inline)) INLINE static void runner_iact_rt_gradient(
-    float r2, const float *dx, float hi, float hj, struct part *restrict pi,
-    struct part *restrict pj, float a, float H) {}
+    const float r2, const float dx[3], const float hi, const float hj,
+    struct part *restrict pi, struct part *restrict pj, const float a,
+    const float H) {}
 
 /**
  * @brief Calculate the gradient interaction between particle i and particle j:
@@ -132,8 +136,10 @@ __attribute__((always_inline)) INLINE static void runner_iact_rt_gradient(
  * @param H Current Hubble parameter.
  */
 __attribute__((always_inline)) INLINE static void
-runner_iact_nonsym_rt_gradient(float r2, const float *dx, float hi, float hj,
+runner_iact_nonsym_rt_gradient(const float r2, const float dx[3],
+                               const float hi, const float hj,
                                struct part *restrict pi,
-                               struct part *restrict pj, float a, float H) {}
+                               struct part *restrict pj, const float a,
+                               const float H) {}
 
 #endif /* SWIFT_RT_IACT_NONE_H */
diff --git a/src/runner.h b/src/runner.h
index 73682b306dfa54e907b6a27ac0c9ec599b85d4d5..33c51c23618caafba53fca76b0ecd0380f02d1ad 100644
--- a/src/runner.h
+++ b/src/runner.h
@@ -101,7 +101,8 @@ void runner_do_black_holes_swallow_ghost(struct runner *r, struct cell *c,
 void runner_do_sinks_density_ghost(struct runner *r, struct cell *c, int timer);
 void runner_do_init_grav(struct runner *r, struct cell *c, int timer);
 void runner_do_hydro_sort(struct runner *r, struct cell *c, int flag,
-                          int cleanup, int rt_requests_sort, int clock);
+                          const int cleanup, const int lock,
+                          const int rt_requests_sort, const int clock);
 void runner_do_stars_sort(struct runner *r, struct cell *c, int flag,
                           int cleanup, int clock);
 void runner_do_all_hydro_sort(struct runner *r, struct cell *c);
diff --git a/src/runner_doiact_functions_hydro.h b/src/runner_doiact_functions_hydro.h
index 1bcf1af20777fd97ad7756603ffa3fb5babc3168..d8bf99b2f6cd4bb1c77935512164bbde99f678ed 100644
--- a/src/runner_doiact_functions_hydro.h
+++ b/src/runner_doiact_functions_hydro.h
@@ -123,8 +123,7 @@ void DOPAIR1_NAIVE(struct runner *r, struct cell *restrict ci,
         runner_iact_nonsym_chemistry(r2, dx, hi, hj, pi, pj, a, H);
         runner_iact_nonsym_pressure_floor(r2, dx, hi, hj, pi, pj, a, H);
         runner_iact_nonsym_star_formation(r2, dx, hi, hj, pi, pj, a, H);
-        runner_iact_nonsym_sink(r2, dx, hi, hj, pi, pj, a, H,
-                                e->sink_properties->cut_off_radius);
+        runner_iact_nonsym_sink(r2, dx, hi, hj, pi, pj, a, H);
 #endif
 #if (FUNCTION_TASK_LOOP == TASK_LOOP_FORCE)
         runner_iact_nonsym_timebin(r2, dx, hi, hj, pi, pj, a, H);
@@ -145,8 +144,7 @@ void DOPAIR1_NAIVE(struct runner *r, struct cell *restrict ci,
         runner_iact_nonsym_chemistry(r2, dx, hj, hi, pj, pi, a, H);
         runner_iact_nonsym_pressure_floor(r2, dx, hj, hi, pj, pi, a, H);
         runner_iact_nonsym_star_formation(r2, dx, hj, hi, pj, pi, a, H);
-        runner_iact_nonsym_sink(r2, dx, hj, hi, pj, pi, a, H,
-                                e->sink_properties->cut_off_radius);
+        runner_iact_nonsym_sink(r2, dx, hi, hj, pi, pj, a, H);
 #endif
 #if (FUNCTION_TASK_LOOP == TASK_LOOP_FORCE)
         runner_iact_nonsym_timebin(r2, dx, hj, hi, pj, pi, a, H);
@@ -260,8 +258,7 @@ void DOPAIR2_NAIVE(struct runner *r, struct cell *restrict ci,
           runner_iact_chemistry(r2, dx, hi, hj, pi, pj, a, H);
           runner_iact_pressure_floor(r2, dx, hi, hj, pi, pj, a, H);
           runner_iact_star_formation(r2, dx, hi, hj, pi, pj, a, H);
-          runner_iact_sink(r2, dx, hi, hj, pi, pj, a, H,
-                           e->sink_properties->cut_off_radius);
+          runner_iact_sink(r2, dx, hi, hj, pi, pj, a, H);
 #endif
 #if (FUNCTION_TASK_LOOP == TASK_LOOP_FORCE)
           runner_iact_timebin(r2, dx, hi, hj, pi, pj, a, H);
@@ -277,8 +274,7 @@ void DOPAIR2_NAIVE(struct runner *r, struct cell *restrict ci,
           runner_iact_nonsym_chemistry(r2, dx, hi, hj, pi, pj, a, H);
           runner_iact_nonsym_pressure_floor(r2, dx, hi, hj, pi, pj, a, H);
           runner_iact_nonsym_star_formation(r2, dx, hi, hj, pi, pj, a, H);
-          runner_iact_nonsym_sink(r2, dx, hi, hj, pi, pj, a, H,
-                                  e->sink_properties->cut_off_radius);
+          runner_iact_nonsym_sink(r2, dx, hi, hj, pi, pj, a, H);
 #endif
 #if (FUNCTION_TASK_LOOP == TASK_LOOP_FORCE)
           runner_iact_nonsym_timebin(r2, dx, hi, hj, pi, pj, a, H);
@@ -298,8 +294,7 @@ void DOPAIR2_NAIVE(struct runner *r, struct cell *restrict ci,
           runner_iact_nonsym_chemistry(r2, dx, hj, hi, pj, pi, a, H);
           runner_iact_nonsym_pressure_floor(r2, dx, hj, hi, pj, pi, a, H);
           runner_iact_nonsym_star_formation(r2, dx, hj, hi, pj, pi, a, H);
-          runner_iact_nonsym_sink(r2, dx, hj, hi, pj, pi, a, H,
-                                  e->sink_properties->cut_off_radius);
+          runner_iact_nonsym_sink(r2, dx, hi, hj, pi, pj, a, H);
 #endif
 #if (FUNCTION_TASK_LOOP == TASK_LOOP_FORCE)
           runner_iact_nonsym_timebin(r2, dx, hj, hi, pj, pi, a, H);
@@ -402,8 +397,7 @@ void DOSELF1_NAIVE(struct runner *r, struct cell *restrict c) {
         runner_iact_chemistry(r2, dx, hi, hj, pi, pj, a, H);
         runner_iact_pressure_floor(r2, dx, hi, hj, pi, pj, a, H);
         runner_iact_star_formation(r2, dx, hi, hj, pi, pj, a, H);
-        runner_iact_sink(r2, dx, hi, hj, pi, pj, a, H,
-                         e->sink_properties->cut_off_radius);
+        runner_iact_sink(r2, dx, hi, hj, pi, pj, a, H);
 #endif
 #if (FUNCTION_TASK_LOOP == TASK_LOOP_FORCE)
         runner_iact_timebin(r2, dx, hi, hj, pi, pj, a, H);
@@ -419,8 +413,7 @@ void DOSELF1_NAIVE(struct runner *r, struct cell *restrict c) {
         runner_iact_nonsym_chemistry(r2, dx, hi, hj, pi, pj, a, H);
         runner_iact_nonsym_pressure_floor(r2, dx, hi, hj, pi, pj, a, H);
         runner_iact_nonsym_star_formation(r2, dx, hi, hj, pi, pj, a, H);
-        runner_iact_nonsym_sink(r2, dx, hi, hj, pi, pj, a, H,
-                                e->sink_properties->cut_off_radius);
+        runner_iact_nonsym_sink(r2, dx, hi, hj, pi, pj, a, H);
 #endif
 #if (FUNCTION_TASK_LOOP == TASK_LOOP_FORCE)
         runner_iact_nonsym_timebin(r2, dx, hi, hj, pi, pj, a, H);
@@ -440,8 +433,7 @@ void DOSELF1_NAIVE(struct runner *r, struct cell *restrict c) {
         runner_iact_nonsym_chemistry(r2, dx, hj, hi, pj, pi, a, H);
         runner_iact_nonsym_pressure_floor(r2, dx, hj, hi, pj, pi, a, H);
         runner_iact_nonsym_star_formation(r2, dx, hj, hi, pj, pi, a, H);
-        runner_iact_nonsym_sink(r2, dx, hj, hi, pj, pi, a, H,
-                                e->sink_properties->cut_off_radius);
+        runner_iact_nonsym_sink(r2, dx, hj, hi, pj, pi, a, H);
 #endif
 #if (FUNCTION_TASK_LOOP == TASK_LOOP_FORCE)
         runner_iact_nonsym_timebin(r2, dx, hj, hi, pj, pi, a, H);
@@ -543,8 +535,7 @@ void DOSELF2_NAIVE(struct runner *r, struct cell *restrict c) {
         runner_iact_chemistry(r2, dx, hi, hj, pi, pj, a, H);
         runner_iact_pressure_floor(r2, dx, hi, hj, pi, pj, a, H);
         runner_iact_star_formation(r2, dx, hi, hj, pi, pj, a, H);
-        runner_iact_sink(r2, dx, hi, hj, pi, pj, a, H,
-                         e->sink_properties->cut_off_radius);
+        runner_iact_sink(r2, dx, hi, hj, pi, pj, a, H);
 #endif
 #if (FUNCTION_TASK_LOOP == TASK_LOOP_FORCE)
         runner_iact_timebin(r2, dx, hi, hj, pi, pj, a, H);
@@ -560,8 +551,7 @@ void DOSELF2_NAIVE(struct runner *r, struct cell *restrict c) {
         runner_iact_nonsym_chemistry(r2, dx, hi, hj, pi, pj, a, H);
         runner_iact_nonsym_pressure_floor(r2, dx, hi, hj, pi, pj, a, H);
         runner_iact_nonsym_star_formation(r2, dx, hi, hj, pi, pj, a, H);
-        runner_iact_nonsym_sink(r2, dx, hi, hj, pi, pj, a, H,
-                                e->sink_properties->cut_off_radius);
+        runner_iact_nonsym_sink(r2, dx, hi, hj, pi, pj, a, H);
 #endif
 #if (FUNCTION_TASK_LOOP == TASK_LOOP_FORCE)
         runner_iact_nonsym_timebin(r2, dx, hi, hj, pi, pj, a, H);
@@ -581,8 +571,7 @@ void DOSELF2_NAIVE(struct runner *r, struct cell *restrict c) {
         runner_iact_nonsym_chemistry(r2, dx, hj, hi, pj, pi, a, H);
         runner_iact_nonsym_pressure_floor(r2, dx, hj, hi, pj, pi, a, H);
         runner_iact_nonsym_star_formation(r2, dx, hj, hi, pj, pi, a, H);
-        runner_iact_nonsym_sink(r2, dx, hj, hi, pj, pi, a, H,
-                                e->sink_properties->cut_off_radius);
+        runner_iact_nonsym_sink(r2, dx, hj, hi, pj, pi, a, H);
 #endif
 #if (FUNCTION_TASK_LOOP == TASK_LOOP_FORCE)
         runner_iact_nonsym_timebin(r2, dx, hj, hi, pj, pi, a, H);
@@ -683,8 +672,7 @@ void DOPAIR_SUBSET_NAIVE(struct runner *r, struct cell *restrict ci,
         runner_iact_nonsym_chemistry(r2, dx, hi, pj->h, pi, pj, a, H);
         runner_iact_nonsym_pressure_floor(r2, dx, hi, pj->h, pi, pj, a, H);
         runner_iact_nonsym_star_formation(r2, dx, hi, pj->h, pi, pj, a, H);
-        runner_iact_nonsym_sink(r2, dx, hi, pj->h, pi, pj, a, H,
-                                e->sink_properties->cut_off_radius);
+        runner_iact_nonsym_sink(r2, dx, hi, pj->h, pi, pj, a, H);
 #endif
 #if (FUNCTION_TASK_LOOP == TASK_LOOP_FORCE)
         runner_iact_nonsym_timebin(r2, dx, hi, pj->h, pi, pj, a, H);
@@ -792,8 +780,7 @@ void DOPAIR_SUBSET(struct runner *r, struct cell *restrict ci,
           runner_iact_nonsym_chemistry(r2, dx, hi, hj, pi, pj, a, H);
           runner_iact_nonsym_pressure_floor(r2, dx, hi, hj, pi, pj, a, H);
           runner_iact_nonsym_star_formation(r2, dx, hi, hj, pi, pj, a, H);
-          runner_iact_nonsym_sink(r2, dx, hi, hj, pi, pj, a, H,
-                                  e->sink_properties->cut_off_radius);
+          runner_iact_nonsym_sink(r2, dx, hi, hj, pi, pj, a, H);
 #endif
 #if (FUNCTION_TASK_LOOP == TASK_LOOP_FORCE)
           runner_iact_nonsym_timebin(r2, dx, hi, hj, pi, pj, a, H);
@@ -858,8 +845,7 @@ void DOPAIR_SUBSET(struct runner *r, struct cell *restrict ci,
           runner_iact_nonsym_chemistry(r2, dx, hi, hj, pi, pj, a, H);
           runner_iact_nonsym_pressure_floor(r2, dx, hi, hj, pi, pj, a, H);
           runner_iact_nonsym_star_formation(r2, dx, hi, hj, pi, pj, a, H);
-          runner_iact_nonsym_sink(r2, dx, hi, hj, pi, pj, a, H,
-                                  e->sink_properties->cut_off_radius);
+          runner_iact_nonsym_sink(r2, dx, hi, hj, pi, pj, a, H);
 #endif
 #if (FUNCTION_TASK_LOOP == TASK_LOOP_FORCE)
           runner_iact_nonsym_timebin(r2, dx, hi, hj, pi, pj, a, H);
@@ -1025,8 +1011,7 @@ void DOSELF_SUBSET(struct runner *r, struct cell *restrict ci,
         runner_iact_nonsym_chemistry(r2, dx, hi, hj, pi, pj, a, H);
         runner_iact_nonsym_pressure_floor(r2, dx, hi, hj, pi, pj, a, H);
         runner_iact_nonsym_star_formation(r2, dx, hi, hj, pi, pj, a, H);
-        runner_iact_nonsym_sink(r2, dx, hi, hj, pi, pj, a, H,
-                                e->sink_properties->cut_off_radius);
+        runner_iact_nonsym_sink(r2, dx, hi, hj, pi, pj, a, H);
 #endif
 #if (FUNCTION_TASK_LOOP == TASK_LOOP_FORCE)
         runner_iact_nonsym_timebin(r2, dx, hi, hj, pi, pj, a, H);
@@ -1208,8 +1193,7 @@ void DOPAIR1(struct runner *r, struct cell *ci, struct cell *cj, const int sid,
           runner_iact_nonsym_chemistry(r2, dx, hi, hj, pi, pj, a, H);
           runner_iact_nonsym_pressure_floor(r2, dx, hi, hj, pi, pj, a, H);
           runner_iact_nonsym_star_formation(r2, dx, hi, hj, pi, pj, a, H);
-          runner_iact_nonsym_sink(r2, dx, hi, hj, pi, pj, a, H,
-                                  e->sink_properties->cut_off_radius);
+          runner_iact_nonsym_sink(r2, dx, hi, hj, pi, pj, a, H);
 #endif
 #if (FUNCTION_TASK_LOOP == TASK_LOOP_FORCE)
           runner_iact_nonsym_timebin(r2, dx, hi, hj, pi, pj, a, H);
@@ -1308,8 +1292,7 @@ void DOPAIR1(struct runner *r, struct cell *ci, struct cell *cj, const int sid,
           runner_iact_nonsym_chemistry(r2, dx, hj, hi, pj, pi, a, H);
           runner_iact_nonsym_pressure_floor(r2, dx, hj, hi, pj, pi, a, H);
           runner_iact_nonsym_star_formation(r2, dx, hj, hi, pj, pi, a, H);
-          runner_iact_nonsym_sink(r2, dx, hj, hi, pj, pi, a, H,
-                                  e->sink_properties->cut_off_radius);
+          runner_iact_nonsym_sink(r2, dx, hj, hi, pj, pi, a, H);
 #endif
 #if (FUNCTION_TASK_LOOP == TASK_LOOP_FORCE)
           runner_iact_nonsym_timebin(r2, dx, hj, hi, pj, pi, a, H);
@@ -1629,8 +1612,7 @@ void DOPAIR2(struct runner *r, struct cell *ci, struct cell *cj, const int sid,
           runner_iact_nonsym_chemistry(r2, dx, hj, hi, pj, pi, a, H);
           runner_iact_nonsym_pressure_floor(r2, dx, hj, hi, pj, pi, a, H);
           runner_iact_nonsym_star_formation(r2, dx, hj, hi, pj, pi, a, H);
-          runner_iact_nonsym_sink(r2, dx, hj, hi, pj, pi, a, H,
-                                  e->sink_properties->cut_off_radius);
+          runner_iact_nonsym_sink(r2, dx, hj, hi, pj, pi, a, H);
 #endif
 #if (FUNCTION_TASK_LOOP == TASK_LOOP_FORCE)
           runner_iact_nonsym_timebin(r2, dx, hj, hi, pj, pi, a, H);
@@ -1712,8 +1694,7 @@ void DOPAIR2(struct runner *r, struct cell *ci, struct cell *cj, const int sid,
             runner_iact_chemistry(r2, dx, hi, hj, pi, pj, a, H);
             runner_iact_pressure_floor(r2, dx, hi, hj, pi, pj, a, H);
             runner_iact_star_formation(r2, dx, hi, hj, pi, pj, a, H);
-            runner_iact_sink(r2, dx, hi, hj, pi, pj, a, H,
-                             e->sink_properties->cut_off_radius);
+            runner_iact_sink(r2, dx, hi, hj, pi, pj, a, H);
 #endif
 #if (FUNCTION_TASK_LOOP == TASK_LOOP_FORCE)
             runner_iact_timebin(r2, dx, hi, hj, pi, pj, a, H);
@@ -1728,8 +1709,7 @@ void DOPAIR2(struct runner *r, struct cell *ci, struct cell *cj, const int sid,
             runner_iact_nonsym_chemistry(r2, dx, hi, hj, pi, pj, a, H);
             runner_iact_nonsym_pressure_floor(r2, dx, hi, hj, pi, pj, a, H);
             runner_iact_nonsym_star_formation(r2, dx, hi, hj, pi, pj, a, H);
-            runner_iact_nonsym_sink(r2, dx, hi, hj, pi, pj, a, H,
-                                    e->sink_properties->cut_off_radius);
+            runner_iact_nonsym_sink(r2, dx, hi, hj, pi, pj, a, H);
 #endif
 #if (FUNCTION_TASK_LOOP == TASK_LOOP_FORCE)
             runner_iact_nonsym_timebin(r2, dx, hi, hj, pi, pj, a, H);
@@ -1844,8 +1824,7 @@ void DOPAIR2(struct runner *r, struct cell *ci, struct cell *cj, const int sid,
           runner_iact_nonsym_chemistry(r2, dx, hi, hj, pi, pj, a, H);
           runner_iact_nonsym_pressure_floor(r2, dx, hi, hj, pi, pj, a, H);
           runner_iact_nonsym_star_formation(r2, dx, hi, hj, pi, pj, a, H);
-          runner_iact_nonsym_sink(r2, dx, hi, hj, pi, pj, a, H,
-                                  e->sink_properties->cut_off_radius);
+          runner_iact_nonsym_sink(r2, dx, hi, hj, pi, pj, a, H);
 #endif
 #if (FUNCTION_TASK_LOOP == TASK_LOOP_FORCE)
           runner_iact_nonsym_timebin(r2, dx, hi, hj, pi, pj, a, H);
@@ -1929,8 +1908,7 @@ void DOPAIR2(struct runner *r, struct cell *ci, struct cell *cj, const int sid,
             runner_iact_chemistry(r2, dx, hj, hi, pj, pi, a, H);
             runner_iact_pressure_floor(r2, dx, hj, hi, pj, pi, a, H);
             runner_iact_star_formation(r2, dx, hj, hi, pj, pi, a, H);
-            runner_iact_sink(r2, dx, hj, hi, pj, pi, a, H,
-                             e->sink_properties->cut_off_radius);
+            runner_iact_sink(r2, dx, hj, hi, pj, pi, a, H);
 #endif
 #if (FUNCTION_TASK_LOOP == TASK_LOOP_FORCE)
             runner_iact_timebin(r2, dx, hj, hi, pj, pi, a, H);
@@ -1945,8 +1923,7 @@ void DOPAIR2(struct runner *r, struct cell *ci, struct cell *cj, const int sid,
             runner_iact_nonsym_chemistry(r2, dx, hj, hi, pj, pi, a, H);
             runner_iact_nonsym_pressure_floor(r2, dx, hj, hi, pj, pi, a, H);
             runner_iact_nonsym_star_formation(r2, dx, hj, hi, pj, pi, a, H);
-            runner_iact_nonsym_sink(r2, dx, hj, hi, pj, pi, a, H,
-                                    e->sink_properties->cut_off_radius);
+            runner_iact_nonsym_sink(r2, dx, hj, hi, pj, pi, a, H);
 #endif
 #if (FUNCTION_TASK_LOOP == TASK_LOOP_FORCE)
             runner_iact_nonsym_timebin(r2, dx, hj, hi, pj, pi, a, H);
@@ -2151,8 +2128,7 @@ void DOSELF1(struct runner *r, struct cell *restrict c) {
           runner_iact_nonsym_chemistry(r2, dx, hj, hi, pj, pi, a, H);
           runner_iact_nonsym_pressure_floor(r2, dx, hj, hi, pj, pi, a, H);
           runner_iact_nonsym_star_formation(r2, dx, hj, hi, pj, pi, a, H);
-          runner_iact_nonsym_sink(r2, dx, hj, hi, pj, pi, a, H,
-                                  e->sink_properties->cut_off_radius);
+          runner_iact_nonsym_sink(r2, dx, hj, hi, pj, pi, a, H);
 #endif
 #if (FUNCTION_TASK_LOOP == TASK_LOOP_FORCE)
           runner_iact_nonsym_timebin(r2, dx, hj, hi, pj, pi, a, H);
@@ -2213,8 +2189,7 @@ void DOSELF1(struct runner *r, struct cell *restrict c) {
             runner_iact_chemistry(r2, dx, hi, hj, pi, pj, a, H);
             runner_iact_pressure_floor(r2, dx, hi, hj, pi, pj, a, H);
             runner_iact_star_formation(r2, dx, hi, hj, pi, pj, a, H);
-            runner_iact_sink(r2, dx, hi, hj, pi, pj, a, H,
-                             e->sink_properties->cut_off_radius);
+            runner_iact_sink(r2, dx, hi, hj, pi, pj, a, H);
 #endif
 #if (FUNCTION_TASK_LOOP == TASK_LOOP_FORCE)
             runner_iact_timebin(r2, dx, hi, hj, pi, pj, a, H);
@@ -2230,8 +2205,7 @@ void DOSELF1(struct runner *r, struct cell *restrict c) {
             runner_iact_nonsym_chemistry(r2, dx, hi, hj, pi, pj, a, H);
             runner_iact_nonsym_pressure_floor(r2, dx, hi, hj, pi, pj, a, H);
             runner_iact_nonsym_star_formation(r2, dx, hi, hj, pi, pj, a, H);
-            runner_iact_nonsym_sink(r2, dx, hi, hj, pi, pj, a, H,
-                                    e->sink_properties->cut_off_radius);
+            runner_iact_nonsym_sink(r2, dx, hi, hj, pi, pj, a, H);
 #endif
 #if (FUNCTION_TASK_LOOP == TASK_LOOP_FORCE)
             runner_iact_nonsym_timebin(r2, dx, hi, hj, pi, pj, a, H);
@@ -2251,8 +2225,7 @@ void DOSELF1(struct runner *r, struct cell *restrict c) {
             runner_iact_nonsym_chemistry(r2, dx, hj, hi, pj, pi, a, H);
             runner_iact_nonsym_pressure_floor(r2, dx, hj, hi, pj, pi, a, H);
             runner_iact_nonsym_star_formation(r2, dx, hj, hi, pj, pi, a, H);
-            runner_iact_nonsym_sink(r2, dx, hj, hi, pj, pi, a, H,
-                                    e->sink_properties->cut_off_radius);
+            runner_iact_nonsym_sink(r2, dx, hj, hi, pj, pi, a, H);
 #endif
 #if (FUNCTION_TASK_LOOP == TASK_LOOP_FORCE)
             runner_iact_nonsym_timebin(r2, dx, hj, hi, pj, pi, a, H);
@@ -2395,8 +2368,7 @@ void DOSELF2(struct runner *r, struct cell *restrict c) {
           runner_iact_nonsym_chemistry(r2, dx, hj, hi, pj, pi, a, H);
           runner_iact_nonsym_pressure_floor(r2, dx, hj, hi, pj, pi, a, H);
           runner_iact_nonsym_star_formation(r2, dx, hj, hi, pj, pi, a, H);
-          runner_iact_nonsym_sink(r2, dx, hj, hi, pj, pi, a, H,
-                                  e->sink_properties->cut_off_radius);
+          runner_iact_nonsym_sink(r2, dx, hj, hi, pj, pi, a, H);
 #endif
 #if (FUNCTION_TASK_LOOP == TASK_LOOP_FORCE)
           runner_iact_nonsym_timebin(r2, dx, hj, hi, pj, pi, a, H);
@@ -2452,8 +2424,7 @@ void DOSELF2(struct runner *r, struct cell *restrict c) {
             runner_iact_chemistry(r2, dx, hi, hj, pi, pj, a, H);
             runner_iact_pressure_floor(r2, dx, hi, hj, pi, pj, a, H);
             runner_iact_star_formation(r2, dx, hi, hj, pi, pj, a, H);
-            runner_iact_sink(r2, dx, hi, hj, pi, pj, a, H,
-                             e->sink_properties->cut_off_radius);
+            runner_iact_sink(r2, dx, hi, hj, pi, pj, a, H);
 #endif
 #if (FUNCTION_TASK_LOOP == TASK_LOOP_FORCE)
             runner_iact_timebin(r2, dx, hi, hj, pi, pj, a, H);
@@ -2468,8 +2439,7 @@ void DOSELF2(struct runner *r, struct cell *restrict c) {
             runner_iact_nonsym_chemistry(r2, dx, hi, hj, pi, pj, a, H);
             runner_iact_nonsym_pressure_floor(r2, dx, hi, hj, pi, pj, a, H);
             runner_iact_nonsym_star_formation(r2, dx, hi, hj, pi, pj, a, H);
-            runner_iact_nonsym_sink(r2, dx, hi, hj, pi, pj, a, H,
-                                    e->sink_properties->cut_off_radius);
+            runner_iact_nonsym_sink(r2, dx, hi, hj, pi, pj, a, H);
 #endif
 #if (FUNCTION_TASK_LOOP == TASK_LOOP_FORCE)
             runner_iact_nonsym_timebin(r2, dx, hi, hj, pi, pj, a, H);
diff --git a/src/runner_doiact_functions_sinks.h b/src/runner_doiact_functions_sinks.h
index 6ff95ba1deefd310b28f4e24d30bc62d23b5663c..87b993882a496767f2ebcd080572803883ae013e 100644
--- a/src/runner_doiact_functions_sinks.h
+++ b/src/runner_doiact_functions_sinks.h
@@ -59,8 +59,8 @@ void DOSELF1_SINKS(struct runner *r, struct cell *c, int timer) {
       /* Skip inactive particles */
       if (!sink_is_active(si, e)) continue;
 
-      const float ri = si->r_cut;
-      const float ri2 = ri * ri;
+      const float hi = si->h;
+      const float hig2 = hi * hi * kernel_gamma2;
       const float six[3] = {(float)(si->x[0] - c->loc[0]),
                             (float)(si->x[1] - c->loc[1]),
                             (float)(si->x[2] - c->loc[2])};
@@ -90,8 +90,8 @@ void DOSELF1_SINKS(struct runner *r, struct cell *c, int timer) {
           error("Particle pj not drifted to current time");
 #endif
 
-        if (r2 < ri2) {
-          IACT_SINKS_GAS(r2, dx, ri, hj, si, pj, with_cosmology, cosmo,
+        if (r2 < hig2) {
+          IACT_SINKS_GAS(r2, dx, hi, hj, si, pj, with_cosmology, cosmo,
                          e->gravity_properties, e->sink_properties,
                          e->ti_current, e->time);
         }
@@ -112,8 +112,8 @@ void DOSELF1_SINKS(struct runner *r, struct cell *c, int timer) {
     /* Skip inactive particles */
     if (!sink_is_active(si, e)) continue;
 
-    const float ri = si->r_cut;
-    const float ri2 = ri * ri;
+    const float hi = si->h;
+    const float hig2 = hi * hi * kernel_gamma2;
     const float six[3] = {(float)(si->x[0] - c->loc[0]),
                           (float)(si->x[1] - c->loc[1]),
                           (float)(si->x[2] - c->loc[2])};
@@ -126,8 +126,8 @@ void DOSELF1_SINKS(struct runner *r, struct cell *c, int timer) {
 
       /* Get a pointer to the jth particle. */
       struct sink *restrict sj = &sinks[sjd];
-      const float rj = sj->r_cut;
-      const float rj2 = rj * rj;
+      const float hj = sj->h;
+      const float hjg2 = hj * hj * kernel_gamma2;
 
       /* Early abort? */
       if (sink_is_inhibited(sj, e)) continue;
@@ -142,13 +142,13 @@ void DOSELF1_SINKS(struct runner *r, struct cell *c, int timer) {
 #ifdef SWIFT_DEBUG_CHECKS
       /* Check that particles have been drifted to the current time */
       if (si->ti_drift != e->ti_current)
-        error("Particle bi not drifted to current time");
+        error("Particle si not drifted to current time");
       if (sj->ti_drift != e->ti_current)
         error("Particle bj not drifted to current time");
 #endif
 
-      if (r2 < ri2 || r2 < rj2) {
-        IACT_SINKS_SINK(r2, dx, ri, rj, si, sj, with_cosmology, cosmo,
+      if (r2 < hig2 || r2 < hjg2) {
+        IACT_SINKS_SINK(r2, dx, hi, hj, si, sj, with_cosmology, cosmo,
                         e->gravity_properties, e->sink_properties,
                         e->ti_current, e->time);
       }
@@ -210,8 +210,8 @@ void DO_NONSYM_PAIR1_SINKS_NAIVE(struct runner *r, struct cell *restrict ci,
       /* Skip inactive particles */
       if (!sink_is_active(si, e)) continue;
 
-      const float ri = si->r_cut;
-      const float ri2 = ri * ri;
+      const float hi = si->h;
+      const float hig2 = hi * hi * kernel_gamma2;
       const float six[3] = {(float)(si->x[0] - (cj->loc[0] + shift[0])),
                             (float)(si->x[1] - (cj->loc[1] + shift[1])),
                             (float)(si->x[2] - (cj->loc[2] + shift[2]))};
@@ -241,8 +241,8 @@ void DO_NONSYM_PAIR1_SINKS_NAIVE(struct runner *r, struct cell *restrict ci,
           error("Particle pj not drifted to current time");
 #endif
 
-        if (r2 < ri2) {
-          IACT_SINKS_GAS(r2, dx, ri, hj, si, pj, with_cosmology, cosmo,
+        if (r2 < hig2) {
+          IACT_SINKS_GAS(r2, dx, hi, hj, si, pj, with_cosmology, cosmo,
                          e->gravity_properties, e->sink_properties,
                          e->ti_current, e->time);
         }
@@ -266,8 +266,8 @@ void DO_NONSYM_PAIR1_SINKS_NAIVE(struct runner *r, struct cell *restrict ci,
     /* Skip inactive particles */
     if (!sink_is_active(si, e)) continue;
 
-    const float ri = si->r_cut;
-    const float ri2 = ri * ri;
+    const float hi = si->h;
+    const float hig2 = hi * hi * kernel_gamma2;
     const float six[3] = {(float)(si->x[0] - (cj->loc[0] + shift[0])),
                           (float)(si->x[1] - (cj->loc[1] + shift[1])),
                           (float)(si->x[2] - (cj->loc[2] + shift[2]))};
@@ -277,8 +277,8 @@ void DO_NONSYM_PAIR1_SINKS_NAIVE(struct runner *r, struct cell *restrict ci,
 
       /* Get a pointer to the jth particle. */
       struct sink *restrict sj = &sinks_j[sjd];
-      const float rj = sj->r_cut;
-      const float rj2 = rj * rj;
+      const float hj = sj->h;
+      const float hjg2 = hj * hj * kernel_gamma2;
 
       /* Skip inhibited particles. */
       if (sink_is_inhibited(sj, e)) continue;
@@ -298,8 +298,8 @@ void DO_NONSYM_PAIR1_SINKS_NAIVE(struct runner *r, struct cell *restrict ci,
         error("Particle sj not drifted to current time");
 #endif
 
-      if (r2 < ri2 || r2 < rj2) {
-        IACT_SINKS_SINK(r2, dx, ri, rj, si, sj, with_cosmology, cosmo,
+      if (r2 < hig2 || r2 < hjg2) {
+        IACT_SINKS_SINK(r2, dx, hi, hj, si, sj, with_cosmology, cosmo,
                         e->gravity_properties, e->sink_properties,
                         e->ti_current, e->time);
       }
@@ -337,6 +337,299 @@ void DOPAIR1_SINKS_NAIVE(struct runner *r, struct cell *restrict ci,
   if (timer) TIMER_TOC(TIMER_DOPAIR_SINKS);
 }
 
+/**
+ * @brief Compute the interactions between a cell pair, but only for the
+ *      given indices in ci.
+ *
+ * Version using a brute-force algorithm.
+ *
+ * @param r The #runner.
+ * @param ci The first #cell.
+ * @param sinks_i The #sink to interact with @c cj.
+ * @param ind The list of indices of particles in @c ci to interact with.
+ * @param scount The number of particles in @c ind.
+ * @param cj The second #cell.
+ * @param shift The shift vector to apply to the particles in ci.
+ */
+void DOPAIR1_SUBSET_SINKS_NAIVE(struct runner *r, struct cell *restrict ci,
+                                struct sink *restrict sinks_i,
+                                int *restrict ind, const int scount,
+                                struct cell *restrict cj, const double *shift) {
+
+#ifdef SWIFT_DEBUG_CHECKS
+  if (ci->nodeID != engine_rank) error("Should be run on a different node");
+#endif
+
+  const struct engine *e = r->e;
+  const struct cosmology *cosmo = e->cosmology;
+  const int with_cosmology = e->policy & engine_policy_cosmology;
+  const int count_j = cj->hydro.count;
+  struct part *restrict parts_j = cj->hydro.parts;
+
+  /* Early abort? */
+  if (count_j == 0) return;
+
+  /* Loop over the parts_i. */
+  for (int sid = 0; sid < scount; sid++) {
+
+    /* Get a hold of the ith part in ci. */
+    struct sink *restrict si = &sinks_i[ind[sid]];
+
+    const double six = si->x[0] - (shift[0]);
+    const double siy = si->x[1] - (shift[1]);
+    const double siz = si->x[2] - (shift[2]);
+    const float hi = si->h;
+    const float hig2 = hi * hi * kernel_gamma2;
+
+#ifdef SWIFT_DEBUG_CHECKS
+    if (!sink_is_active(si, e))
+      error("Trying to correct smoothing length of inactive particle !");
+#endif
+
+    /* Loop over the parts in cj. */
+    for (int pjd = 0; pjd < count_j; pjd++) {
+
+      /* Get a pointer to the jth particle. */
+      struct part *restrict pj = &parts_j[pjd];
+
+      /* Skip inhibited particles */
+      if (part_is_inhibited(pj, e)) continue;
+
+      const double pjx = pj->x[0];
+      const double pjy = pj->x[1];
+      const double pjz = pj->x[2];
+      const float hj = pj->h;
+
+      /* Compute the pairwise distance. */
+      const float dx[3] = {(float)(six - pjx), (float)(siy - pjy),
+                           (float)(siz - pjz)};
+      const float r2 = dx[0] * dx[0] + dx[1] * dx[1] + dx[2] * dx[2];
+
+#ifdef SWIFT_DEBUG_CHECKS
+      /* Check that particles have been drifted to the current time */
+      if (pj->ti_drift != e->ti_current)
+        error("Particle pj not drifted to current time");
+#endif
+      /* Hit or miss? */
+      if (r2 < hig2) {
+        IACT_SINKS_GAS(r2, dx, hi, hj, si, pj, with_cosmology, cosmo,
+                       e->gravity_properties, e->sink_properties, e->ti_current,
+                       e->time);
+      }
+    } /* loop over the parts in cj. */
+  } /* loop over the parts in ci. */
+}
+
+/**
+ * @brief Compute the interactions between a cell pair, but only for the
+ *      given indices in ci.
+ *
+ * @param r The #runner.
+ * @param ci The first #cell.
+ * @param sinks The #sink to interact.
+ * @param ind The list of indices of particles in @c ci to interact with.
+ * @param scount The number of particles in @c ind.
+ */
+void DOSELF1_SUBSET_SINKS(struct runner *r, struct cell *restrict ci,
+                          struct sink *restrict sinks, int *restrict ind,
+                          const int scount) {
+
+#ifdef SWIFT_DEBUG_CHECKS
+  if (ci->nodeID != engine_rank) error("Should be run on a different node");
+#endif
+
+  const struct engine *e = r->e;
+  const struct cosmology *cosmo = e->cosmology;
+  const int with_cosmology = e->policy & engine_policy_cosmology;
+  const int count_i = ci->hydro.count;
+  struct part *restrict parts_j = ci->hydro.parts;
+
+  /* Early abort? */
+  if (count_i == 0) return;
+
+  /* Loop over the parts in ci. */
+  for (int sid = 0; sid < scount; sid++) {
+
+    /* Get a hold of the ith part in ci. */
+    struct sink *si = &sinks[ind[sid]];
+    const float six[3] = {(float)(si->x[0] - ci->loc[0]),
+                          (float)(si->x[1] - ci->loc[1]),
+                          (float)(si->x[2] - ci->loc[2])};
+    const float hi = si->h;
+    const float hig2 = hi * hi * kernel_gamma2;
+
+#ifdef SWIFT_DEBUG_CHECKS
+    if (!sink_is_active(si, e)) error("Inactive particle in subset function!");
+#endif
+
+    /* Loop over the parts in cj. */
+    for (int pjd = 0; pjd < count_i; pjd++) {
+
+      /* Get a pointer to the jth particle. */
+      struct part *restrict pj = &parts_j[pjd];
+
+      /* Early abort? */
+      if (part_is_inhibited(pj, e)) continue;
+
+      /* Compute the pairwise distance. */
+      const float pjx[3] = {(float)(pj->x[0] - ci->loc[0]),
+                            (float)(pj->x[1] - ci->loc[1]),
+                            (float)(pj->x[2] - ci->loc[2])};
+      const float dx[3] = {six[0] - pjx[0], six[1] - pjx[1], six[2] - pjx[2]};
+      const float r2 = dx[0] * dx[0] + dx[1] * dx[1] + dx[2] * dx[2];
+
+#ifdef SWIFT_DEBUG_CHECKS
+      /* Check that particles have been drifted to the current time */
+      if (pj->ti_drift != e->ti_current)
+        error("Particle pj not drifted to current time");
+#endif
+
+      /* Hit or miss? */
+      if (r2 < hig2) {
+        IACT_SINKS_GAS(r2, dx, hi, pj->h, si, pj, with_cosmology, cosmo,
+                       e->gravity_properties, e->sink_properties, e->ti_current,
+                       e->time);
+      }
+    } /* loop over the parts in cj. */
+  } /* loop over the parts in ci. */
+}
+
+/**
+ * @brief Determine which version of DOSELF1_SUBSET_SINKS needs to be called
+ * depending on the optimisation level.
+ *
+ * @param r The #runner.
+ * @param ci The first #cell.
+ * @param sinks The #sink to interact.
+ * @param ind The list of indices of particles in @c ci to interact with.
+ * @param scount The number of particles in @c ind.
+ */
+void DOSELF1_SUBSET_BRANCH_SINKS(struct runner *r, struct cell *restrict ci,
+                                 struct sink *restrict sinks, int *restrict ind,
+                                 const int scount) {
+
+  DOSELF1_SUBSET_SINKS(r, ci, sinks, ind, scount);
+}
+
+/**
+ * @brief Determine which version of DOPAIR1_SUBSET_SINKS needs to be called
+ * depending on the orientation of the cells or whether DOPAIR1_SUBSET_SINKS
+ * needs to be called at all.
+ *
+ * @param r The #runner.
+ * @param ci The first #cell.
+ * @param sinks_i The #sink to interact with @c cj.
+ * @param ind The list of indices of particles in @c ci to interact with.
+ * @param scount The number of particles in @c ind.
+ * @param cj The second #cell.
+ */
+void DOPAIR1_SUBSET_BRANCH_SINKS(struct runner *r, struct cell *restrict ci,
+                                 struct sink *restrict sinks_i,
+                                 int *restrict ind, int const scount,
+                                 struct cell *restrict cj) {
+
+  const struct engine *e = r->e;
+
+  /* Anything to do here? */
+  if (cj->hydro.count == 0) return;
+
+  /* Get the relative distance between the pairs, wrapping. */
+  double shift[3] = {0.0, 0.0, 0.0};
+  for (int k = 0; k < 3; k++) {
+    if (cj->loc[k] - ci->loc[k] < -e->s->dim[k] / 2)
+      shift[k] = e->s->dim[k];
+    else if (cj->loc[k] - ci->loc[k] > e->s->dim[k] / 2)
+      shift[k] = -e->s->dim[k];
+  }
+
+  DOPAIR1_SUBSET_SINKS_NAIVE(r, ci, sinks_i, ind, scount, cj, shift);
+}
+
+void DOSUB_SUBSET_SINKS(struct runner *r, struct cell *ci, struct sink *sinks,
+                        int *ind, const int scount, struct cell *cj,
+                        int gettimer) {
+
+  const struct engine *e = r->e;
+  struct space *s = e->s;
+
+  /* Should we even bother? */
+  if (!cell_is_active_sinks(ci, e) &&
+      (cj == NULL || !cell_is_active_sinks(cj, e)))
+    return;
+
+  /* Find out in which sub-cell of ci the parts are. */
+  struct cell *sub = NULL;
+  if (ci->split) {
+    for (int k = 0; k < 8; k++) {
+      if (ci->progeny[k] != NULL) {
+        if (&sinks[ind[0]] >= &ci->progeny[k]->sinks.parts[0] &&
+            &sinks[ind[0]] <
+                &ci->progeny[k]->sinks.parts[ci->progeny[k]->sinks.count]) {
+          sub = ci->progeny[k];
+          break;
+        }
+      }
+    }
+  }
+
+  /* Is this a single cell? */
+  if (cj == NULL) {
+
+    /* Recurse? */
+    if (cell_can_recurse_in_self_sinks_task(ci)) {
+
+      /* Loop over all progeny. */
+      DOSUB_SUBSET_SINKS(r, sub, sinks, ind, scount, NULL, 0);
+      for (int j = 0; j < 8; j++)
+        if (ci->progeny[j] != sub && ci->progeny[j] != NULL)
+          DOSUB_SUBSET_SINKS(r, sub, sinks, ind, scount, ci->progeny[j], 0);
+
+    }
+
+    /* Otherwise, compute self-interaction. */
+    else
+      DOSELF1_SUBSET_BRANCH_SINKS(r, ci, sinks, ind, scount);
+  } /* self-interaction. */
+
+  /* Otherwise, it's a pair interaction. */
+  else {
+
+    /* Recurse? */
+    if (cell_can_recurse_in_pair_sinks_task(ci, cj) &&
+        cell_can_recurse_in_pair_sinks_task(cj, ci)) {
+
+      /* Get the type of pair and flip ci/cj if needed. */
+      double shift[3] = {0.0, 0.0, 0.0};
+      const int sid = space_getsid_and_swap_cells(s, &ci, &cj, shift);
+
+      struct cell_split_pair *csp = &cell_split_pairs[sid];
+      for (int k = 0; k < csp->count; k++) {
+        const int pid = csp->pairs[k].pid;
+        const int pjd = csp->pairs[k].pjd;
+        if (ci->progeny[pid] == sub && cj->progeny[pjd] != NULL)
+          DOSUB_SUBSET_SINKS(r, ci->progeny[pid], sinks, ind, scount,
+                             cj->progeny[pjd], 0);
+        if (ci->progeny[pid] != NULL && cj->progeny[pjd] == sub)
+          DOSUB_SUBSET_SINKS(r, cj->progeny[pjd], sinks, ind, scount,
+                             ci->progeny[pid], 0);
+      }
+    }
+
+    /* Otherwise, compute the pair directly. */
+    else if (cell_is_active_sinks(ci, e) && cj->hydro.count > 0) {
+
+      /* Do any of the cells need to be drifted first? */
+      if (cell_is_active_sinks(ci, e)) {
+        if (!cell_are_sink_drifted(ci, e)) error("Cell should be drifted!");
+        if (!cell_are_part_drifted(cj, e)) error("Cell should be drifted!");
+      }
+
+      DOPAIR1_SUBSET_BRANCH_SINKS(r, ci, sinks, ind, scount, cj);
+    }
+
+  } /* otherwise, pair interaction. */
+}
+
 /**
  * @brief Wrapper to runner_doself_sinks_swallow
  *
@@ -359,8 +652,8 @@ void DOSELF1_BRANCH_SINKS(struct runner *r, struct cell *c) {
   if (!cell_is_active_sinks(c, e)) return;
 
   /* Did we mess up the recursion? */
-  if (c->sinks.r_cut_max_old > c->dmin)
-    error("Cell smaller than the cut off radius");
+  if (c->sinks.h_max_old * kernel_gamma > c->dmin)
+    error("Cell smaller than the cut off radius or smoothing length");
 
   DOSELF1_SINKS(r, c, 1);
 }
diff --git a/src/runner_doiact_sinks.h b/src/runner_doiact_sinks.h
index 8195924b5ea6197cf3cfed57ea4c4a02d6aa5cd4..3b9351a2ca876f199c9157fed493b0a78b81fe98 100644
--- a/src/runner_doiact_sinks.h
+++ b/src/runner_doiact_sinks.h
@@ -38,6 +38,27 @@
 #define _DOPAIR1_SINKS_NAIVE(f) PASTE(runner_dopair_sinks_naive, f)
 #define DOPAIR1_SINKS_NAIVE _DOPAIR1_SINKS_NAIVE(FUNCTION)
 
+#define _DOPAIR1_SUBSET_SINKS(f) PASTE(runner_dopair_subset_sinks, f)
+#define DOPAIR1_SUBSET_SINKS _DOPAIR1_SUBSET_SINKS(FUNCTION)
+
+#define _DOPAIR1_SUBSET_SINKS_NAIVE(f) \
+  PASTE(runner_dopair_subset_sinks_naive, f)
+#define DOPAIR1_SUBSET_SINKS_NAIVE _DOPAIR1_SUBSET_SINKS_NAIVE(FUNCTION)
+
+#define _DOSELF1_SUBSET_SINKS(f) PASTE(runner_doself_subset_sinks, f)
+#define DOSELF1_SUBSET_SINKS _DOSELF1_SUBSET_SINKS(FUNCTION)
+
+#define _DOSELF1_SUBSET_BRANCH_SINKS(f) \
+  PASTE(runner_doself_subset_branch_sinks, f)
+#define DOSELF1_SUBSET_BRANCH_SINKS _DOSELF1_SUBSET_BRANCH_SINKS(FUNCTION)
+
+#define _DOPAIR1_SUBSET_BRANCH_SINKS(f) \
+  PASTE(runner_dopair_subset_branch_sinks, f)
+#define DOPAIR1_SUBSET_BRANCH_SINKS _DOPAIR1_SUBSET_BRANCH_SINKS(FUNCTION)
+
+#define _DOSUB_SUBSET_SINKS(f) PASTE(runner_dosub_subset_sinks, f)
+#define DOSUB_SUBSET_SINKS _DOSUB_SUBSET_SINKS(FUNCTION)
+
 #define _DOSELF1_BRANCH_SINKS(f) PASTE(runner_doself_branch_sinks, f)
 #define DOSELF1_BRANCH_SINKS _DOSELF1_BRANCH_SINKS(FUNCTION)
 
@@ -74,3 +95,15 @@ void DOPAIR1_BRANCH_SINKS(struct runner *r, struct cell *ci, struct cell *cj);
 void DOSUB_SELF1_SINKS(struct runner *r, struct cell *ci, int gettimer);
 void DOSUB_PAIR1_SINKS(struct runner *r, struct cell *ci, struct cell *cj,
                        int gettimer);
+
+void DOSELF1_SUBSET_BRANCH_SINKS(struct runner *r, struct cell *restrict ci,
+                                 struct sink *restrict sinks, int *restrict ind,
+                                 const int scount);
+void DOPAIR1_SUBSET_BRANCH_SINKS(struct runner *r, struct cell *restrict ci,
+                                 struct sink *restrict sinks_i,
+                                 int *restrict ind, int const scount,
+                                 struct cell *restrict cj);
+
+void DOSUB_SUBSET_SINKS(struct runner *r, struct cell *ci, struct sink *sinks,
+                        int *ind, const int scount, struct cell *cj,
+                        int gettimer);
diff --git a/src/runner_ghost.c b/src/runner_ghost.c
index 38aaf9fd523af8a7c7b72a7dc8d561600f5c71e1..ff53dd758cbb5a59244e73cfdf694829292e8aa4 100644
--- a/src/runner_ghost.c
+++ b/src/runner_ghost.c
@@ -63,6 +63,13 @@
 #undef FUNCTION_TASK_LOOP
 #undef FUNCTION
 
+/* Import the sink density loop functions. */
+#define FUNCTION density
+#define FUNCTION_TASK_LOOP TASK_LOOP_DENSITY
+#include "runner_doiact_sinks.h"
+#undef FUNCTION_TASK_LOOP
+#undef FUNCTION
+
 /**
  * @brief Intermediate task after the density to check that the smoothing
  * lengths are correct.
@@ -383,6 +390,9 @@ void runner_do_stars_ghost(struct runner *r, struct cell *c, int timer) {
 
         /* We now have a particle whose smoothing length has converged */
 
+        /* Set the correct depth */
+        cell_set_spart_h_depth(sp, c);
+
         /* Check if h_max has increased */
         h_max = max(h_max, sp->h);
         h_max_active = max(h_max_active, sp->h);
@@ -805,6 +815,9 @@ void runner_do_black_holes_density_ghost(struct runner *r, struct cell *c,
 
         black_holes_reset_feedback(bp);
 
+        /* Set the correct depth */
+        cell_set_bpart_h_depth(bp, c);
+
         /* Check if h_max has increased */
         h_max = max(h_max, bp->h);
         h_max_active = max(h_max_active, bp->h);
@@ -1424,6 +1437,9 @@ void runner_do_ghost(struct runner *r, struct cell *c, int timer) {
 
         /* We now have a particle whose smoothing length has converged */
 
+        /* Set the correct depth */
+        cell_set_part_h_depth(p, c);
+
         /* Check if h_max has increased */
         h_max = max(h_max, p->h);
         h_max_active = max(h_max_active, p->h);
@@ -1710,8 +1726,9 @@ void runner_do_rt_ghost2(struct runner *r, struct cell *c, int timer) {
 }
 
 /**
- * @brief Intermediate task after the density to finish density calculation
- *  and calculate accretion rates for the particle swallowing step
+ * @brief Intermediate task after the density to check that the smoothing
+ * lengths are correct, finish density calculations
+ * and calculate accretion rates for the particle swallowing step
  *
  * @param r The runner thread.
  * @param c The cell.
@@ -1724,6 +1741,16 @@ void runner_do_sinks_density_ghost(struct runner *r, struct cell *c,
   const struct engine *e = r->e;
   const struct cosmology *cosmo = e->cosmology;
   const int with_cosmology = e->policy & engine_policy_cosmology;
+  const float sinks_h_max = e->hydro_properties->h_max;
+  const float sinks_h_min = e->hydro_properties->h_min;
+  const float eps = e->sink_properties->h_tolerance;
+  const float sinks_eta_dim = pow_dimension(e->sink_properties->eta_neighbours);
+  const int max_smoothing_iter = e->hydro_properties->max_smoothing_iterations;
+  int redo = 0, scount = 0;
+
+  /* Running value of the maximal smoothing length */
+  float h_max = c->sinks.h_max;
+  float h_max_active = c->sinks.h_max_active;
 
   TIMER_TIC;
 
@@ -1736,58 +1763,359 @@ void runner_do_sinks_density_ghost(struct runner *r, struct cell *c,
     for (int k = 0; k < 8; k++) {
       if (c->progeny[k] != NULL) {
         runner_do_sinks_density_ghost(r, c->progeny[k], 0);
+
+        /* Update h_max */
+        h_max = max(h_max, c->progeny[k]->sinks.h_max);
+        h_max_active = max(h_max_active, c->progeny[k]->sinks.h_max_active);
       }
     }
   } else {
 
     /* Init the list of active particles that have to be updated. */
     int *sid = NULL;
+    float *h_0 = NULL;
+    float *left = NULL;
+    float *right = NULL;
     if ((sid = (int *)malloc(sizeof(int) * c->sinks.count)) == NULL)
       error("Can't allocate memory for sid.");
-
-    int scount = 0;
+    if ((h_0 = (float *)malloc(sizeof(float) * c->sinks.count)) == NULL)
+      error("Can't allocate memory for h_0.");
+    if ((left = (float *)malloc(sizeof(float) * c->sinks.count)) == NULL)
+      error("Can't allocate memory for left.");
+    if ((right = (float *)malloc(sizeof(float) * c->sinks.count)) == NULL)
+      error("Can't allocate memory for right.");
     for (int k = 0; k < c->sinks.count; k++)
       if (sink_is_active(&sinks[k], e)) {
         sid[scount] = k;
+        h_0[scount] = sinks[k].h;
+        left[scount] = 0.f;
+        right[scount] = sinks_h_max;
         ++scount;
       }
 
-    /* Loop over the remaining active parts in this cell. */
-    for (int i = 0; i < scount; i++) {
+    if (e->sink_properties->use_fixed_r_cut) {
+      /* If we're using a fixed cutoff rather than a smoothing length, just
+       * finish up the density task and leave sp->h untouched. */
 
-      /* Get a direct pointer on the part. */
-      struct sink *sp = &sinks[sid[i]];
+      /* Loop over the active sinks in this cell. */
+      for (int i = 0; i < scount; i++) {
+
+        /* Get a direct pointer on the part. */
+        struct sink *sp = &sinks[sid[i]];
 
 #ifdef SWIFT_DEBUG_CHECKS
-      /* Is this part within the timestep? */
-      if (!sink_is_active(sp, e)) error("Ghost applied to inactive particle");
+        /* Is this part within the timestep? */
+        if (!sink_is_active(sp, e)) error("Ghost applied to inactive particle");
 #endif
 
-      /* Finish the density calculation */
-      sink_end_density(sp, cosmo);
+        /* Finish the density calculation */
+        sink_end_density(sp, cosmo);
 
-      if (sp->num_ngbs == 0) {
-        sinks_sink_has_no_neighbours(sp, cosmo);
+        /* Set these variables to the fixed cutoff radius for the rest of the
+         * ghost task */
+        h_max = sp->h;
+        h_max_active = sp->h;
       }
 
-      /* Get particle time-step */
-      double dt;
-      if (with_cosmology) {
-        const integertime_t ti_step = get_integer_timestep(sp->time_bin);
-        const integertime_t ti_begin =
-            get_integer_time_begin(e->ti_current - 1, sp->time_bin);
-
-        dt = cosmology_get_delta_time(e->cosmology, ti_begin,
-                                      ti_begin + ti_step);
-      } else {
-        dt = get_timestep(sp->time_bin, e->time_base);
+    } else {
+      /* Otherwise we need to iterate to update the smoothing lengths */
+
+      /* While there are particles that need to be updated... */
+      for (int num_reruns = 0; scount > 0 && num_reruns < max_smoothing_iter;
+           num_reruns++) {
+
+        /* Reset the redo-count. */
+        redo = 0;
+
+        /* Loop over the remaining active parts in this cell. */
+        for (int i = 0; i < scount; i++) {
+
+          /* Get a direct pointer on the part. */
+          struct sink *sp = &sinks[sid[i]];
+
+#ifdef SWIFT_DEBUG_CHECKS
+          /* Is this part within the timestep? */
+          if (!sink_is_active(sp, e))
+            error("Ghost applied to inactive particle");
+#endif
+
+          /* Get some useful values */
+          const float h_init = h_0[i];
+          const float h_old = sp->h;
+          const float h_old_dim = pow_dimension(h_old);
+          const float h_old_dim_minus_one = pow_dimension_minus_one(h_old);
+
+          float h_new;
+          int has_no_neighbours = 0;
+
+          if (sp->density.wcount <
+              1.e-5 * kernel_root) { /* No neighbours case */
+
+            /* Flag that there were no neighbours */
+            has_no_neighbours = 1;
+
+            /* Double h and try again */
+            h_new = 2.f * h_old;
+
+          } else {
+
+            /* Finish the density calculation */
+            sink_end_density(sp, cosmo);
+
+            /* Compute one step of the Newton-Raphson scheme */
+            const float n_sum = sp->density.wcount * h_old_dim;
+            const float n_target = sinks_eta_dim;
+            const float f = n_sum - n_target;
+            const float f_prime =
+                sp->density.wcount_dh * h_old_dim +
+                hydro_dimension * sp->density.wcount * h_old_dim_minus_one;
+
+            /* Improve the bisection bounds */
+            if (n_sum < n_target)
+              left[i] = max(left[i], h_old);
+            else if (n_sum > n_target)
+              right[i] = min(right[i], h_old);
+
+#ifdef SWIFT_DEBUG_CHECKS
+            /* Check the validity of the left and right bounds */
+            if (left[i] > right[i])
+              error("Invalid left (%e) and right (%e)", left[i], right[i]);
+#endif
+
+            /* Skip if h is already h_max and we don't have enough neighbours
+             */
+            /* Same if we are below h_min */
+            if (((sp->h >= sinks_h_max) && (f < 0.f)) ||
+                ((sp->h <= sinks_h_min) && (f > 0.f))) {
+
+              /* Ok, we are done with this particle */
+              continue;
+            }
+
+            /* Normal case: Use Newton-Raphson to get a better value of h */
+
+            /* Avoid floating point exception from f_prime = 0 */
+            h_new = h_old - f / (f_prime + FLT_MIN);
+
+            /* Be verbose about the particles that struggle to converge */
+            if (num_reruns > max_smoothing_iter - 10) {
+
+              message(
+                  "Smoothing length convergence problem: iter=%d p->id=%lld "
+                  "h_init=%12.8e h_old=%12.8e h_new=%12.8e f=%f f_prime=%f "
+                  "n_sum=%12.8e n_target=%12.8e left=%12.8e right=%12.8e",
+                  num_reruns, sp->id, h_init, h_old, h_new, f, f_prime, n_sum,
+                  n_target, left[i], right[i]);
+            }
+
+            /* Safety check: truncate to the range [ h_old/2 , 2h_old ]. */
+            h_new = min(h_new, 2.f * h_old);
+            h_new = max(h_new, 0.5f * h_old);
+
+            /* Verify that we are actually progrssing towards the answer */
+            h_new = max(h_new, left[i]);
+            h_new = min(h_new, right[i]);
+          }
+
+          /* Check whether the particle has an inappropriate smoothing length
+           */
+          if (fabsf(h_new - h_old) > eps * h_old) {
+
+            /* Ok, correct then */
+
+            /* Case where we have been oscillating around the solution */
+            if ((h_new == left[i] && h_old == right[i]) ||
+                (h_old == left[i] && h_new == right[i])) {
+
+              /* Bisect the remaining interval */
+              sp->h = pow_inv_dimension(
+                  0.5f * (pow_dimension(left[i]) + pow_dimension(right[i])));
+
+            } else {
+
+              /* Normal case */
+              sp->h = h_new;
+            }
+
+            /* If below the absolute maximum, try again */
+            if (sp->h < sinks_h_max && sp->h > sinks_h_min) {
+
+              /* Flag for another round of fun */
+              sid[redo] = sid[i];
+              h_0[redo] = h_0[i];
+              left[redo] = left[i];
+              right[redo] = right[i];
+              redo += 1;
+
+              /* Re-initialise everything */
+              sink_init_sink(sp);
+
+              /* Off we go ! */
+              continue;
+
+            } else if (sp->h <= sinks_h_min) {
+
+              /* Ok, this particle is a lost cause... */
+              sp->h = sinks_h_min;
+
+            } else if (sp->h >= sinks_h_max) {
+
+              /* Ok, this particle is a lost cause... */
+              sp->h = sinks_h_max;
+
+              /* Do some damage control if no neighbours at all were found */
+              if (has_no_neighbours) {
+                sinks_sink_has_no_neighbours(sp, cosmo);
+              }
+
+            } else {
+              error(
+                  "Fundamental problem with the smoothing length iteration "
+                  "logic.");
+            }
+          }
+
+          /* We now have a particle whose smoothing length has converged */
+
+          /* Set the correct depth */
+          cell_set_sink_h_depth(sp, c);
+
+          /* Check if h_max has increased */
+          h_max = max(h_max, sp->h);
+          h_max_active = max(h_max_active, sp->h);
+        }
+
+        /* We now need to treat the particles whose smoothing length had not
+         * converged again */
+
+        /* Re-set the counter for the next loop (potentially). */
+        scount = redo;
+        if (scount > 0) {
+
+          /* Climb up the cell hierarchy. */
+          for (struct cell *finger = c; finger != NULL;
+               finger = finger->parent) {
+
+            /* Run through this cell's density interactions. */
+            for (struct link *l = finger->sinks.density; l != NULL;
+                 l = l->next) {
+
+#ifdef SWIFT_DEBUG_CHECKS
+              if (l->t->ti_run < r->e->ti_current)
+                error("Density task should have been run.");
+#endif
+
+              /* Self-interaction? */
+              if (l->t->type == task_type_self)
+                runner_doself_subset_branch_sinks_density(r, finger, sinks, sid,
+                                                          scount);
+
+              /* Otherwise, pair interaction? */
+              else if (l->t->type == task_type_pair) {
+
+                /* Left or right? */
+                if (l->t->ci == finger)
+                  runner_dopair_subset_branch_sinks_density(
+                      r, finger, sinks, sid, scount, l->t->cj);
+                else
+                  runner_dopair_subset_branch_sinks_density(
+                      r, finger, sinks, sid, scount, l->t->ci);
+              }
+
+              /* Otherwise, sub-self interaction? */
+              else if (l->t->type == task_type_sub_self)
+                runner_dosub_subset_sinks_density(r, finger, sinks, sid, scount,
+                                                  NULL, 1);
+
+              /* Otherwise, sub-pair interaction? */
+              else if (l->t->type == task_type_sub_pair) {
+
+                /* Left or right? */
+                if (l->t->ci == finger)
+                  runner_dosub_subset_sinks_density(r, finger, sinks, sid,
+                                                    scount, l->t->cj, 1);
+                else
+                  runner_dosub_subset_sinks_density(r, finger, sinks, sid,
+                                                    scount, l->t->ci, 1);
+              }
+            }
+          }
+        }
+      }
+
+      if (scount) {
+        warning(
+            "Smoothing length failed to converge for the following sink "
+            "particles:");
+        for (int i = 0; i < scount; i++) {
+          struct sink *sp = &sinks[sid[i]];
+          warning("ID: %lld, h: %g, wcount: %g", sp->id, sp->h,
+                  sp->density.wcount);
+        }
+
+        error("Smoothing length failed to converge on %i particles.", scount);
       }
 
-      /* Calculate the accretion rate and accreted mass this timestep, for use
-       * in swallow loop */
-      sink_prepare_swallow(sp, e->sink_properties, e->physical_constants,
-                           e->cosmology, e->cooling_func, e->entropy_floor,
-                           e->time, with_cosmology, dt, e->ti_current);
+      /* Be clean */
+      free(left);
+      free(right);
+      free(sid);
+      free(h_0);
+    }
+
+    /* We need one more quick loop over the sinks to run prepare_swallow */
+    for (int i = 0; i < c->sinks.count; i++) {
+
+      /* Get a direct pointer on the part. */
+      struct sink *sp = &sinks[i];
+
+      if (sink_is_active(sp, e)) {
+
+        /* Get particle time-step */
+        double dt;
+        if (with_cosmology) {
+          const integertime_t ti_step = get_integer_timestep(sp->time_bin);
+          const integertime_t ti_begin =
+              get_integer_time_begin(e->ti_current - 1, sp->time_bin);
+
+          dt = cosmology_get_delta_time(e->cosmology, ti_begin,
+                                        ti_begin + ti_step);
+        } else {
+          dt = get_timestep(sp->time_bin, e->time_base);
+        }
+
+        /* Calculate the accretion rate and accreted mass this timestep, for use
+         * in swallow loop */
+        sink_prepare_swallow(sp, e->sink_properties, e->physical_constants,
+                             e->cosmology, e->cooling_func, e->entropy_floor,
+                             e->time, with_cosmology, dt, e->ti_current);
+      }
+    }
+  }
+
+  /* Update h_max */
+  c->sinks.h_max = h_max;
+  c->sinks.h_max_active = h_max_active;
+
+#ifdef SWIFT_DEBUG_CHECKS
+  for (int i = 0; i < c->sinks.count; ++i) {
+    const struct sink *sp = &c->sinks.parts[i];
+    const float h = c->sinks.parts[i].h;
+    if (sink_is_inhibited(sp, e)) continue;
+
+    if (h > c->sinks.h_max)
+      error("Particle has h larger than h_max (id=%lld)", sp->id);
+    if (sink_is_active(sp, e) && h > c->sinks.h_max_active)
+      error("Active particle has h larger than h_max_active (id=%lld)", sp->id);
+  }
+#endif
+
+  /* The ghost may not always be at the top level.
+   * Therefore we need to update h_max between the super- and top-levels */
+  if (c->sinks.density_ghost) {
+    for (struct cell *tmp = c->parent; tmp != NULL; tmp = tmp->parent) {
+      atomic_max_f(&tmp->sinks.h_max, h_max);
+      atomic_max_f(&tmp->sinks.h_max_active, h_max_active);
     }
   }
 
diff --git a/src/runner_main.c b/src/runner_main.c
index f2e96410cddcd839dce5a88a426217201444d62a..f85f07e0699b7c4d12d63bc32ec7d50bd3e34de5 100644
--- a/src/runner_main.c
+++ b/src/runner_main.c
@@ -416,7 +416,8 @@ void *runner_main(void *data) {
           runner_do_hydro_sort(
               r, ci, t->flags,
               ci->hydro.dx_max_sort_old > space_maxreldx * ci->dmin,
-              cell_get_flag(ci, cell_flag_rt_requests_sort), 1);
+              /*lock=*/0, cell_get_flag(ci, cell_flag_rt_requests_sort),
+              /*clock=*/1);
           /* Reset the sort flags as our work here is done. */
           t->flags = 0;
           break;
@@ -427,7 +428,8 @@ void *runner_main(void *data) {
            * don't have rt_sort tasks. */
           runner_do_hydro_sort(
               r, ci, t->flags,
-              ci->hydro.dx_max_sort_old > space_maxreldx * ci->dmin, 1, 1);
+              ci->hydro.dx_max_sort_old > space_maxreldx * ci->dmin,
+              /*lock=*/0, /*rt_requests_sorts=*/1, /*clock=*/1);
           /* Reset the sort flags as our work here is done. */
           t->flags = 0;
           break;
diff --git a/src/runner_sort.c b/src/runner_sort.c
index 59b345490cb547124b480b502cde6f3f31f78e89..500c1878c52943d49ce43a6710c4484059ee974e 100644
--- a/src/runner_sort.c
+++ b/src/runner_sort.c
@@ -201,7 +201,8 @@ RUNNER_CHECK_SORTS(stars)
  *      for recursive calls.
  */
 void runner_do_hydro_sort(struct runner *r, struct cell *c, int flags,
-                          int cleanup, int rt_requests_sort, int clock) {
+                          const int cleanup, const int lock,
+                          const int rt_requests_sort, const int clock) {
 
   struct sort_entry *fingers[8];
   const int count = c->hydro.count;
@@ -215,6 +216,9 @@ void runner_do_hydro_sort(struct runner *r, struct cell *c, int flags,
   if (c->hydro.super == NULL) error("Task called above the super level!!!");
 #endif
 
+  /* Should we lock the cell once more? */
+  if (lock) lock_lock(&c->hydro.extra_sort_lock);
+
   /* We need to do the local sorts plus whatever was requested further up. */
   flags |= c->hydro.do_sort;
   if (cleanup) {
@@ -223,8 +227,14 @@ void runner_do_hydro_sort(struct runner *r, struct cell *c, int flags,
     flags &= ~c->hydro.sorted;
   }
   if (flags == 0 && !cell_get_flag(c, cell_flag_do_hydro_sub_sort) &&
-      !cell_get_flag(c, cell_flag_do_rt_sub_sort))
+      !cell_get_flag(c, cell_flag_do_rt_sub_sort)) {
+
+    /* Unlock before returning */
+    if (lock && lock_unlock(&c->hydro.extra_sort_lock) != 0)
+      error("Impossible to unlock the cell!");
+
     return;
+  }
 
   /* Check that the particles have been moved to the current time */
   if (flags && !cell_are_part_drifted(c, r->e)) {
@@ -268,7 +278,7 @@ void runner_do_hydro_sort(struct runner *r, struct cell *c, int flags,
               r, c->progeny[k], flags,
               cleanup && (c->progeny[k]->hydro.dx_max_sort_old >
                           space_maxreldx * c->progeny[k]->dmin),
-              rt_requests_sort, 0);
+              lock, rt_requests_sort, /*clock=*/0);
           dx_max_sort = max(dx_max_sort, c->progeny[k]->hydro.dx_max_sort);
           dx_max_sort_old =
               max(dx_max_sort_old, c->progeny[k]->hydro.dx_max_sort_old);
@@ -429,6 +439,10 @@ void runner_do_hydro_sort(struct runner *r, struct cell *c, int flags,
   cell_clear_flag(c, cell_flag_rt_requests_sort);
   c->hydro.requires_sorts = 0;
 
+  /* Make sure we unlock if necessary */
+  if (lock && lock_unlock(&c->hydro.extra_sort_lock) != 0)
+    error("Impossible to unlock the cell!");
+
   if (clock) TIMER_TOC(timer_dosort);
 }
 
@@ -684,8 +698,8 @@ void runner_do_all_hydro_sort(struct runner *r, struct cell *c) {
   if (c->hydro.super == c) {
 
     /* Sort everything */
-    runner_do_hydro_sort(r, c, 0x1FFF, /*cleanup=*/0, /*rt_requests_sort=*/0,
-                         /*timer=*/0);
+    runner_do_hydro_sort(r, c, 0x1FFF, /*cleanup=*/0, /* lock=*/0,
+                         /*rt_requests_sort=*/0, /*timer=*/0);
 
   } else {
 
diff --git a/src/runner_time_integration.c b/src/runner_time_integration.c
index 37d341af8bae5e4a2b2d6dfd1e1214faa1ec229f..802ff912e16bb44e54f6dd4628ec148174d18841 100644
--- a/src/runner_time_integration.c
+++ b/src/runner_time_integration.c
@@ -1142,6 +1142,9 @@ void runner_do_timestep(struct runner *r, struct cell *c, const int timer) {
     }
   }
 
+  /* Flag something may have changed */
+  if (c->top == c) space_mark_cell_as_updated(r->e->s, c);
+
   /* Store the values. */
   c->hydro.updated = updated;
   c->grav.updated = g_updated;
@@ -1269,6 +1272,9 @@ void runner_do_timestep_collect(struct runner *r, struct cell *c,
     }
   }
 
+  /* Flag something may have changed */
+  if (c->top == c) space_mark_cell_as_updated(r->e->s, c);
+
   /* Store the collected values in the cell. */
   c->hydro.ti_end_min = ti_hydro_end_min;
   c->hydro.ti_beg_max = ti_hydro_beg_max;
@@ -1347,6 +1353,9 @@ void runner_do_limiter(struct runner *r, struct cell *c, int force,
       }
     }
 
+    /* Flag something may have changed */
+    if (c->top == c) space_mark_cell_as_updated(r->e->s, c);
+
     /* Store the updated values */
     c->hydro.ti_end_min = min(c->hydro.ti_end_min, ti_hydro_end_min);
     c->hydro.ti_beg_max = max(c->hydro.ti_beg_max, ti_hydro_beg_max);
@@ -1427,6 +1436,9 @@ void runner_do_limiter(struct runner *r, struct cell *c, int force,
       }
     }
 
+    /* Flag something may have changed */
+    if (c->top == c) space_mark_cell_as_updated(r->e->s, c);
+
     /* Store the updated values */
     c->hydro.ti_end_min = min(c->hydro.ti_end_min, ti_hydro_end_min);
     c->hydro.ti_beg_max = max(c->hydro.ti_beg_max, ti_hydro_beg_max);
@@ -1499,6 +1511,9 @@ void runner_do_sync(struct runner *r, struct cell *c, int force,
       }
     }
 
+    /* Flag something may have changed */
+    if (c->top == c) space_mark_cell_as_updated(r->e->s, c);
+
     /* Store the updated values */
     c->hydro.ti_end_min = min(c->hydro.ti_end_min, ti_hydro_end_min);
     c->hydro.ti_beg_max = max(c->hydro.ti_beg_max, ti_hydro_beg_max);
@@ -1599,6 +1614,9 @@ void runner_do_sync(struct runner *r, struct cell *c, int force,
       }
     }
 
+    /* Flag something may have changed */
+    if (c->top == c) space_mark_cell_as_updated(r->e->s, c);
+
     /* Store the updated values */
     c->hydro.ti_end_min = min(c->hydro.ti_end_min, ti_hydro_end_min);
     c->hydro.ti_beg_max = max(c->hydro.ti_beg_max, ti_hydro_beg_max);
diff --git a/src/sink.c b/src/sink.c
new file mode 100644
index 0000000000000000000000000000000000000000..c3adf40237b51c65ab5eee4e415c490f4e68f03a
--- /dev/null
+++ b/src/sink.c
@@ -0,0 +1,341 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2024 Jonathan Davies (j.j.davies@ljmu.ac.uk)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ ******************************************************************************/
+
+/* Config parameters. */
+#include <config.h>
+
+/* Some standard headers. */
+#include <float.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+/* Local headers. */
+#include "active.h"
+#include "error.h"
+#include "sink_properties.h"
+#include "version.h"
+
+struct exact_density_data {
+  const struct engine *e;
+  const struct space *s;
+  int counter_global;
+};
+
+/**
+ * @brief Mapper function for the exact sink checks.
+ *
+ * @brief map_data The #sinks.
+ * @brief nr_sinks The number of star particles.
+ * @brief extra_data Pointers to the structure containing global interaction
+ * counters.
+ */
+void sink_exact_density_compute_mapper(void *map_data, int nr_sinks,
+                                       void *extra_data) {
+#ifdef SWIFT_SINK_DENSITY_CHECKS
+
+  /* Unpack the data */
+  struct sink *restrict sinks = (struct sink *)map_data;
+  struct exact_density_data *data = (struct exact_density_data *)extra_data;
+  const struct space *s = data->s;
+  const struct engine *e = data->e;
+  const int periodic = s->periodic;
+  const double dim[3] = {s->dim[0], s->dim[1], s->dim[2]};
+  int counter = 0;
+
+  for (int i = 0; i < nr_sinks; ++i) {
+
+    struct sink *si = &sinks[i];
+    const long long id = si->id;
+
+    /* Is the particle active and part of the subset to be tested ? */
+    if (id % SWIFT_SINK_DENSITY_CHECKS == 0 && sink_is_starting(si, e)) {
+
+      /* Get some information about the particle */
+      const double pix[3] = {si->x[0], si->x[1], si->x[2]};
+      const double hi = si->h;
+      const float hi_inv = 1.f / hi;
+      const float hig2 = hi * hi * kernel_gamma2;
+
+      /* Be ready for the calculation */
+      int N_density_exact = 0;
+      double rho_exact = 0.;
+      double n_exact = 0.;
+
+      /* Interact it with all other particles in the space.*/
+      for (int j = 0; j < (int)s->nr_parts; ++j) {
+
+        const struct part *pj = &s->parts[j];
+
+        /* Compute the pairwise distance. */
+        double dx = pj->x[0] - pix[0];
+        double dy = pj->x[1] - pix[1];
+        double dz = pj->x[2] - pix[2];
+
+        /* Now apply periodic BC */
+        if (periodic) {
+          dx = nearest(dx, dim[0]);
+          dy = nearest(dy, dim[1]);
+          dz = nearest(dz, dim[2]);
+        }
+
+        const double r2 = dx * dx + dy * dy + dz * dz;
+
+        /* Interact loop of type 1? */
+        if (r2 < hig2) {
+
+          const float mj = pj->mass;
+
+          float wi, wi_dx;
+
+          /* Kernel function */
+          const float r = sqrtf(r2);
+          const float ui = r * hi_inv;
+          kernel_deval(ui, &wi, &wi_dx);
+
+          /* Flag that we found an inhibited neighbour */
+          if (part_is_inhibited(pj, e)) {
+            si->inhibited_check_exact = 1;
+          } else {
+
+            /* Density */
+            rho_exact += mj * wi;
+
+            /* Number density */
+            n_exact += wi;
+
+            /* Number of neighbours */
+            N_density_exact++;
+          }
+        }
+      }
+
+      /* Store the exact answer */
+      si->N_check_density_exact = N_density_exact;
+      si->rho_check_exact = rho_exact * pow_dimension(hi_inv);
+      si->n_check_exact = n_exact * pow_dimension(hi_inv);
+
+      counter++;
+    }
+  }
+  atomic_add(&data->counter_global, counter);
+
+#else
+  error("Sink checking function called without the corresponding flag.");
+#endif
+}
+
+/**
+ * @brief Compute the exact interactions for a selection of star particles
+ * by running a brute force loop over all the particles in the simulation.
+ *
+ * Will be incorrect over MPI.
+ *
+ * @param s The #space.
+ * @param e The #engine.
+ */
+void sink_exact_density_compute(struct space *s, const struct engine *e) {
+
+#ifdef SWIFT_SINK_DENSITY_CHECKS
+
+  const ticks tic = getticks();
+
+  struct exact_density_data data;
+  data.e = e;
+  data.s = s;
+  data.counter_global = 0;
+
+  threadpool_map(&s->e->threadpool, sink_exact_density_compute_mapper, s->sinks,
+                 s->nr_sinks, sizeof(struct sink), 0, &data);
+
+  if (e->verbose)
+    message("Computed exact densities for %d sinks (took %.3f %s). ",
+            data.counter_global, clocks_from_ticks(getticks() - tic),
+            clocks_getunit());
+#else
+  error("Sink checking function called without the corresponding flag.");
+#endif
+}
+
+/**
+ * @brief Check the star particles' density and force calculations against the
+ * values obtained via the brute-force summation.
+ *
+ * @param s The #space.
+ * @param e The #engine.
+ * @param rel_tol Relative tolerance for the checks
+ */
+void sink_exact_density_check(struct space *s, const struct engine *e,
+                              const double rel_tol) {
+
+#ifdef SWIFT_SINK_DENSITY_CHECKS
+
+  const ticks tic = getticks();
+
+  const struct sink *sinks = s->sinks;
+  const size_t nr_sinks = s->nr_sinks;
+
+  const double eta = e->sink_properties->eta_neighbours;
+  const double N_ngb_target =
+      (4. / 3.) * M_PI * pow_dimension(kernel_gamma * eta);
+  const double N_ngb_max =
+      N_ngb_target + 2. * e->sink_properties->delta_neighbours;
+  const double N_ngb_min =
+      N_ngb_target - 2. * e->sink_properties->delta_neighbours;
+
+  /* File name */
+  char file_name_swift[100];
+  sprintf(file_name_swift, "sink_checks_swift_step%.4d.dat", e->step);
+
+  /* Creare files and write header */
+  FILE *file_swift = fopen(file_name_swift, "w");
+  if (file_swift == NULL) error("Could not create file '%s'.", file_name_swift);
+  fprintf(file_swift, "# Sink accuracy test - SWIFT DENSITIES\n");
+  fprintf(file_swift, "# N= %d\n", SWIFT_SINK_DENSITY_CHECKS);
+  fprintf(file_swift, "# periodic= %d\n", s->periodic);
+  fprintf(file_swift, "# N_ngb_target= %f +/- %f\n", N_ngb_target,
+          e->sink_properties->delta_neighbours);
+  fprintf(file_swift, "# Git Branch: %s\n", git_branch());
+  fprintf(file_swift, "# Git Revision: %s\n", git_revision());
+  fprintf(file_swift, "# %16s %16s %16s %16s %16s %7s %7s %16s %16s %16s\n",
+          "id", "pos[0]", "pos[1]", "pos[2]", "h", "Nd", "Nf", "rho", "n_rho",
+          "N_ngb");
+
+  /* Output particle SWIFT densities */
+  for (size_t i = 0; i < nr_sinks; ++i) {
+
+    const struct sink *si = &sinks[i];
+    const long long id = si->id;
+
+    const double N_ngb = (4. / 3.) * M_PI * kernel_gamma * kernel_gamma *
+                         kernel_gamma * si->h * si->h * si->h * si->n_check;
+
+    if (id % SWIFT_SINK_DENSITY_CHECKS == 0 && sink_is_starting(si, e)) {
+
+      fprintf(
+          file_swift,
+          "%18lld %16.8e %16.8e %16.8e %16.8e %7d %7d %16.8e %16.8e %16.8e\n",
+          id, si->x[0], si->x[1], si->x[2], si->h, si->N_check_density, 0,
+          si->rho_check, si->n_check, N_ngb);
+    }
+  }
+
+  if (e->verbose)
+    message("Written SWIFT densities in file '%s'.", file_name_swift);
+
+  /* Be nice */
+  fclose(file_swift);
+
+  /* File name */
+  char file_name_exact[100];
+  sprintf(file_name_exact, "sink_checks_exact_step%.4d.dat", e->step);
+
+  /* Creare files and write header */
+  FILE *file_exact = fopen(file_name_exact, "w");
+  if (file_exact == NULL) error("Could not create file '%s'.", file_name_exact);
+  fprintf(file_exact, "# Sink accuracy test - EXACT DENSITIES\n");
+  fprintf(file_exact, "# N= %d\n", SWIFT_SINK_DENSITY_CHECKS);
+  fprintf(file_exact, "# periodic= %d\n", s->periodic);
+  fprintf(file_exact, "# N_ngb_target= %f +/- %f\n", N_ngb_target,
+          e->sink_properties->delta_neighbours);
+  fprintf(file_exact, "# Git Branch: %s\n", git_branch());
+  fprintf(file_exact, "# Git Revision: %s\n", git_revision());
+  fprintf(file_exact, "# %16s %16s %16s %16s %16s %7s %7s %16s %16s %16s\n",
+          "id", "pos[0]", "pos[1]", "pos[2]", "h", "Nd", "Nf", "rho_exact",
+          "n_rho_exact", "N_ngb");
+
+  int wrong_rho = 0;
+  int wrong_n_ngb = 0;
+  int counter = 0;
+
+  /* Output particle SWIFT densities */
+  for (size_t i = 0; i < nr_sinks; ++i) {
+
+    const struct sink *si = &sinks[i];
+    const long long id = si->id;
+    const int found_inhibited = si->inhibited_check_exact;
+
+    const double N_ngb = (4. / 3.) * M_PI * kernel_gamma * kernel_gamma *
+                         kernel_gamma * si->h * si->h * si->h *
+                         si->n_check_exact;
+
+    if (id % SWIFT_SINK_DENSITY_CHECKS == 0 && sink_is_starting(si, e)) {
+
+      counter++;
+
+      fprintf(
+          file_exact,
+          "%18lld %16.8e %16.8e %16.8e %16.8e %7d %7d %16.8e %16.8e %16.8e\n",
+          id, si->x[0], si->x[1], si->x[2], si->h, si->N_check_density_exact, 0,
+          si->rho_check_exact, si->n_check_exact, N_ngb);
+
+      /* Check that we did not go above the threshold.
+       * Note that we ignore particles that saw an inhibted particle as a
+       * neighbour as we don't know whether that neighbour became inhibited in
+       * that step or not. */
+      if (!found_inhibited &&
+          si->N_check_density_exact != si->N_check_density &&
+          (fabsf(si->rho_check / si->rho_check_exact - 1.f) > rel_tol ||
+           fabsf(si->rho_check_exact / si->rho_check - 1.f) > rel_tol)) {
+        message("RHO: id=%lld swift=%e exact=%e N_swift=%d N_true=%d", id,
+                si->rho_check, si->rho_check_exact, si->N_check_density,
+                si->N_check_density_exact);
+        wrong_rho++;
+      }
+
+      if (!found_inhibited && (N_ngb > N_ngb_max || N_ngb < N_ngb_min)) {
+
+        message("N_NGB: id=%lld exact=%f N_true=%d N_swift=%d", id, N_ngb,
+                si->N_check_density_exact, si->N_check_density);
+
+        wrong_n_ngb++;
+      }
+    }
+  }
+
+  if (e->verbose)
+    message("Written exact densities in file '%s'.", file_name_exact);
+
+  /* Be nice */
+  fclose(file_exact);
+
+  if (wrong_rho)
+    error(
+        "Density difference larger than the allowed tolerance for %d "
+        "sink particles! (out of %d particles)",
+        wrong_rho, counter);
+  else
+    message("Verified %d sink particles", counter);
+
+  /* if (wrong_n_ngb) */
+  /*   error( */
+  /*       "N_ngb difference larger than the allowed tolerance for %d " */
+  /*       "star particles! (out of %d particles)", */
+  /*       wrong_n_ngb, counter); */
+  /* else */
+  /*   message("Verified %d star particles", counter); */
+
+  if (e->verbose)
+    message("Writting brute-force density files took %.3f %s. ",
+            clocks_from_ticks(getticks() - tic), clocks_getunit());
+
+#else
+  error("Sink checking function called without the corresponding flag.");
+#endif
+}
diff --git a/src/sink.h b/src/sink.h
index 128eaff03e5c9309ec074b6eb84547427a31e5b0..e891283826c4ab84117bf082048666ba0edc7b3f 100644
--- a/src/sink.h
+++ b/src/sink.h
@@ -25,10 +25,19 @@
 /* Select the correct sink model */
 #if defined(SINK_NONE)
 #include "./sink/Default/sink.h"
+#elif defined(SINK_BASIC)
+#include "./sink/Basic/sink.h"
 #elif defined(SINK_GEAR)
 #include "./sink/GEAR/sink.h"
 #else
 #error "Invalid choice of sink model"
 #endif
 
+struct engine;
+struct space;
+
+void sink_exact_density_compute(struct space *s, const struct engine *e);
+void sink_exact_density_check(struct space *s, const struct engine *e,
+                              const double rel_tol);
+
 #endif /* SWIFT_SINK_H */
diff --git a/src/sink/Basic/sink.h b/src/sink/Basic/sink.h
new file mode 100644
index 0000000000000000000000000000000000000000..238a2bf477e7556d0ddf8bd8f895881e7838cb15
--- /dev/null
+++ b/src/sink/Basic/sink.h
@@ -0,0 +1,644 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2024 Jonathan Davies (j.j.davies@ljmu.ac.uk)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ ******************************************************************************/
+#ifndef SWIFT_BASIC_SINK_H
+#define SWIFT_BASIC_SINK_H
+
+#include <float.h>
+
+/* Put pragma if gsl around here */
+#ifdef HAVE_LIBGSL
+#include <gsl/gsl_cdf.h>
+#endif
+
+/* Local includes */
+#include "active.h"
+#include "chemistry.h"
+#include "cooling.h"
+#include "feedback.h"
+#include "minmax.h"
+#include "random.h"
+#include "sink_part.h"
+#include "sink_properties.h"
+#include "star_formation.h"
+
+/**
+ * @brief Computes the time-step of a given sink particle.
+ *
+ * @param sp Pointer to the sink-particle data.
+ */
+__attribute__((always_inline)) INLINE static float sink_compute_timestep(
+    const struct sink* const sink, const struct sink_props* sink_properties,
+    const int with_cosmology, const struct cosmology* cosmo,
+    const struct gravity_props* grav_props, const double time,
+    const double time_base) {
+
+  return FLT_MAX;
+}
+
+/**
+ * @brief Initialises the sink-particles for the first time
+ *
+ * This function is called only once just after the ICs have been
+ * read in to do some conversions.
+ *
+ * @param sp The #sink particle to act upon.
+ * @param sink_props The properties of the sink particles scheme.
+ * @param e The #engine
+ */
+__attribute__((always_inline)) INLINE static void sink_first_init_sink(
+    struct sink* sp, const struct sink_props* sink_props,
+    const struct engine* e) {
+
+  sp->time_bin = 0;
+
+  sp->number_of_gas_swallows = 0;
+  sp->number_of_direct_gas_swallows = 0;
+  sp->number_of_sink_swallows = 0;
+  sp->number_of_direct_sink_swallows = 0;
+  sp->swallowed_angular_momentum[0] = 0.f;
+  sp->swallowed_angular_momentum[1] = 0.f;
+  sp->swallowed_angular_momentum[2] = 0.f;
+
+  /* Initially set the subgrid mass equal to the dynamical mass read from the
+   * ICs. */
+  sp->subgrid_mass = sp->mass;
+
+  sink_mark_sink_as_not_swallowed(&sp->merger_data);
+}
+
+/**
+ * @brief Initialisation of particle data before the hydro density loop.
+ * Note: during initalisation (space_init)
+ *
+ * @param p The #part to act upon.
+ * @param sink_props The properties of the sink particles scheme.
+ */
+__attribute__((always_inline)) INLINE static void sink_init_part(
+    struct part* restrict p, const struct sink_props* sink_props) {}
+
+/**
+ * @brief Initialisation of sink particle data before sink loops.
+ * Note: during initalisation (space_init_sinks)
+ *
+ * @param sp The #sink particle to act upon.
+ */
+__attribute__((always_inline)) INLINE static void sink_init_sink(
+    struct sink* sp) {
+
+  sp->density.wcount = 0.f;
+  sp->density.wcount_dh = 0.f;
+  sp->rho_gas = 0.f;
+  sp->sound_speed_gas = 0.f;
+  sp->velocity_gas[0] = 0.f;
+  sp->velocity_gas[1] = 0.f;
+  sp->velocity_gas[2] = 0.f;
+  sp->ngb_mass = 0.f;
+  sp->num_ngbs = 0;
+  sp->accretion_rate = 0.f;
+  sp->mass_at_start_of_step = sp->mass; /* sp->mass may grow in nibbling mode */
+
+#ifdef DEBUG_INTERACTIONS_SINKS
+  for (int i = 0; i < MAX_NUM_OF_NEIGHBOURS_SINKS; ++i)
+    sp->ids_ngbs_accretion[i] = -1;
+  sp->num_ngb_accretion = 0;
+
+  for (int i = 0; i < MAX_NUM_OF_NEIGHBOURS_SINKS; ++i)
+    sp->ids_ngbs_merger[i] = -1;
+  sp->num_ngb_merger = 0;
+
+  for (int i = 0; i < MAX_NUM_OF_NEIGHBOURS_SINKS; ++i)
+    sp->ids_ngbs_formation[i] = -1;
+  sp->num_ngb_formation = 0;
+#endif
+
+#ifdef SWIFT_SINK_DENSITY_CHECKS
+  sp->N_check_density = 0;
+  sp->N_check_density_exact = 0;
+  sp->rho_check = 0.f;
+  sp->rho_check_exact = 0.f;
+  sp->n_check = 0.f;
+  sp->n_check_exact = 0.f;
+  sp->inhibited_check_exact = 0;
+#endif
+}
+
+/**
+ * @brief Predict additional particle fields forward in time when drifting
+ *
+ * @param sp The #sink.
+ * @param dt_drift The drift time-step for positions.
+ */
+__attribute__((always_inline)) INLINE static void sink_predict_extra(
+    struct sink* restrict sp, float dt_drift) {}
+
+/**
+ * @brief Sets the values to be predicted in the drifts to their values at a
+ * kick time
+ *
+ * @param sp The #sink particle.
+ */
+__attribute__((always_inline)) INLINE static void sink_reset_predicted_values(
+    struct sink* restrict sp) {}
+
+/**
+ * @brief Kick the additional variables
+ *
+ * @param sp The #sink particle to act upon
+ * @param dt The time-step for this kick
+ */
+__attribute__((always_inline)) INLINE static void sink_kick_extra(
+    struct sink* sp, float dt) {}
+
+/**
+ * @brief Finishes the calculation of density on sinks
+ *
+ * @param si The particle to act upon
+ * @param cosmo The current cosmological model.
+ */
+__attribute__((always_inline)) INLINE static void sink_end_density(
+    struct sink* si, const struct cosmology* cosmo) {
+
+  /* Some smoothing length multiples. */
+  const float h = si->h;
+  const float h_inv = 1.0f / h;                       /* 1/h */
+  const float h_inv_dim = pow_dimension(h_inv);       /* 1/h^d */
+  const float h_inv_dim_plus_one = h_inv_dim * h_inv; /* 1/h^(d+1) */
+
+  /* Finish the calculation by inserting the missing h-factors */
+  si->density.wcount *= h_inv_dim;
+  si->density.wcount_dh *= h_inv_dim_plus_one;
+
+  /* Finish the density calculation */
+  si->rho_gas *= h_inv_dim;
+
+  /* For the following, we also have to undo the mass smoothing
+   * (N.B.: bp->velocity_gas is in BH frame, in internal units). */
+  const float rho_inv = 1.f / si->rho_gas;
+  si->sound_speed_gas *= h_inv_dim * rho_inv;
+  si->velocity_gas[0] *= h_inv_dim * rho_inv;
+  si->velocity_gas[1] *= h_inv_dim * rho_inv;
+  si->velocity_gas[2] *= h_inv_dim * rho_inv;
+
+#ifdef SWIFT_SINK_DENSITY_CHECKS
+  si->rho_check *= h_inv_dim;
+  si->n_check *= h_inv_dim;
+#endif
+}
+
+/**
+ * @brief Sets all particle fields to sensible values when the #sink has 0
+ * ngbs.
+ *
+ * @param sp The particle to act upon
+ * @param cosmo The current cosmological model.
+ */
+__attribute__((always_inline)) INLINE static void sinks_sink_has_no_neighbours(
+    struct sink* restrict sp, const struct cosmology* cosmo) {
+
+  warning(
+      "Sink particle with ID %lld treated as having no neighbours (h: %g, "
+      "wcount: %g).",
+      sp->id, sp->h, sp->density.wcount);
+
+  /* Some smoothing length multiples. */
+  const float h = sp->h;
+  const float h_inv = 1.0f / h;                 /* 1/h */
+  const float h_inv_dim = pow_dimension(h_inv); /* 1/h^d */
+
+  /* Re-set problematic values */
+  sp->density.wcount = kernel_root * h_inv_dim;
+  sp->density.wcount_dh = 0.f;
+}
+
+/**
+ * @brief Compute the accretion rate of the sink and any quantities
+ * required swallowing based on an accretion rate
+ *
+ * Adapted from black_holes_prepare_feedback
+ *
+ * @param si The sink particle.
+ * @param props The properties of the sink scheme.
+ * @param constants The physical constants (in internal units).
+ * @param cosmo The cosmological model.
+ * @param cooling Properties of the cooling model.
+ * @param floor_props Properties of the entropy floor.
+ * @param time Time since the start of the simulation (non-cosmo mode).
+ * @param with_cosmology Are we running with cosmology?
+ * @param dt The time-step size (in physical internal units).
+ * @param ti_begin Integer time value at the beginning of timestep
+ */
+__attribute__((always_inline)) INLINE static void sink_prepare_swallow(
+    struct sink* restrict si, const struct sink_props* props,
+    const struct phys_const* constants, const struct cosmology* cosmo,
+    const struct cooling_function_data* cooling,
+    const struct entropy_floor_properties* floor_props, const double time,
+    const int with_cosmology, const double dt, const integertime_t ti_begin) {
+
+  if (dt == 0. || si->rho_gas == 0.) return;
+
+  /* Gather some physical constants (all in internal units) */
+  const double G = constants->const_newton_G;
+
+  /* (Subgrid) mass of the sink (internal units) */
+  const double sink_mass = si->subgrid_mass;
+
+  /* We can now compute the accretion rate (internal units) */
+  /* Standard approach: compute accretion rate for all gas simultaneously.
+   *
+   * Convert the quantities we gathered to physical frame (all internal
+   * units). Note: velocities are already in black hole frame. */
+  const double gas_rho_phys = si->rho_gas * cosmo->a3_inv;
+  const double gas_c_phys = si->sound_speed_gas * cosmo->a_factor_sound_speed;
+  const double gas_c_phys2 = gas_c_phys * gas_c_phys;
+  const double gas_v_phys[3] = {si->velocity_gas[0] * cosmo->a_inv,
+                                si->velocity_gas[1] * cosmo->a_inv,
+                                si->velocity_gas[2] * cosmo->a_inv};
+  const double gas_v_norm2 = gas_v_phys[0] * gas_v_phys[0] +
+                             gas_v_phys[1] * gas_v_phys[1] +
+                             gas_v_phys[2] * gas_v_phys[2];
+
+  const double denominator2 = gas_v_norm2 + gas_c_phys2;
+
+#ifdef SWIFT_DEBUG_CHECKS
+  /* Make sure that the denominator is strictly positive */
+  if (denominator2 <= 0)
+    error(
+        "Invalid denominator for sink particle %lld in Bondi rate "
+        "calculation.",
+        si->id);
+#endif
+
+  const double denominator_inv = 1. / sqrt(denominator2);
+
+  double accr_rate = 4. * M_PI * G * G * sink_mass * sink_mass * gas_rho_phys *
+                     denominator_inv * denominator_inv * denominator_inv;
+
+  /* Integrate forward in time */
+  si->subgrid_mass += accr_rate * dt;
+}
+
+/**
+ * @brief Calculate if the gas has the potential of becoming
+ * a sink.
+ *
+ * Return 0 if no sink formation should occur.
+ * Note: called in runner_do_sink_formation
+ *
+ * @param p the gas particles.
+ * @param xp the additional properties of the gas particles.
+ * @param sink_props the sink properties to use.
+ * @param phys_const the physical constants in internal units.
+ * @param cosmo the cosmological parameters and properties.
+ * @param hydro_props The properties of the hydro scheme.
+ * @param us The internal system of units.
+ * @param cooling The cooling data struct.
+ * @param entropy_floor The entropy_floor properties.
+ *
+ */
+INLINE static int sink_is_forming(
+    const struct part* restrict p, const struct xpart* restrict xp,
+    const struct sink_props* sink_props, const struct phys_const* phys_const,
+    const struct cosmology* cosmo,
+    const struct hydro_props* restrict hydro_props,
+    const struct unit_system* restrict us,
+    const struct cooling_function_data* restrict cooling,
+    const struct entropy_floor_properties* restrict entropy_floor) {
+
+  /* Sink formation is not implemented in this model. */
+  return 0;
+}
+
+/**
+ * @brief Decides whether a particle should be converted into a
+ * sink or not.
+ *
+ * No SF should occur, so return 0.
+ * Note: called in runner_do_sink_formation
+ *
+ * @param p The #part.
+ * @param xp The #xpart.
+ * @param sink_props The properties of the sink model.
+ * @param e The #engine (for random numbers).
+ * @param dt_sink The time-step of this particle
+ * @return 1 if a conversion should be done, 0 otherwise.
+ */
+INLINE static int sink_should_convert_to_sink(
+    const struct part* p, const struct xpart* xp,
+    const struct sink_props* sink_props, const struct engine* e,
+    const double dt_sink) {
+
+  return 0;
+}
+
+/**
+ * @brief Copies the properties of the gas particle over to the
+ * sink particle.
+ *
+ * @param p The gas particles.
+ * @param xp The additional properties of the gas particles.
+ * @param sink the new created #sink particle.
+ * @param e The #engine.
+ * @param sink_props The sink properties to use.
+ * @param cosmo the cosmological parameters and properties.
+ * @param with_cosmology if we run with cosmology.
+ * @param phys_const The physical constants in internal units.
+ * @param hydro_props The hydro properties to use.
+ * @param us The internal unit system.
+ * @param cooling The cooling function to use.
+ */
+INLINE static void sink_copy_properties(
+    const struct part* p, const struct xpart* xp, struct sink* sink,
+    const struct engine* e, const struct sink_props* sink_props,
+    const struct cosmology* cosmo, const int with_cosmology,
+    const struct phys_const* phys_const,
+    const struct hydro_props* restrict hydro_props,
+    const struct unit_system* restrict us,
+    const struct cooling_function_data* restrict cooling) {}
+
+/**
+ * @brief Update the properties of a sink particles by swallowing
+ * a gas particle.
+ *
+ * @param sp The #sink to update.
+ * @param p The #part that is swallowed.
+ * @param xp The #xpart that is swallowed.
+ * @param cosmo The current cosmological model.
+ */
+__attribute__((always_inline)) INLINE static void sink_swallow_part(
+    struct sink* sp, const struct part* p, const struct xpart* xp,
+    const struct cosmology* cosmo) {
+
+  /* Get the current dynamical masses */
+  const float gas_mass = hydro_get_mass(p);
+  const float sink_mass = sp->mass;
+
+  /* Increase the dynamical mass of the sink. */
+  sp->mass += gas_mass;
+  sp->gpart->mass += gas_mass;
+
+  /* Comoving and physical distance between the particles */
+  const float dx[3] = {sp->x[0] - p->x[0], sp->x[1] - p->x[1],
+                       sp->x[2] - p->x[2]};
+  const float dx_physical[3] = {dx[0] * cosmo->a, dx[1] * cosmo->a,
+                                dx[2] * cosmo->a};
+
+  /* Relative velocity between the sink and the part */
+  const float dv[3] = {sp->v[0] - p->v[0], sp->v[1] - p->v[1],
+                       sp->v[2] - p->v[2]};
+
+  const float a = cosmo->a;
+  const float H = cosmo->H;
+  const float a2H = a * a * H;
+
+  /* Calculate the velocity with the Hubble flow */
+  const float v_plus_H_flow[3] = {a2H * dx[0] + dv[0], a2H * dx[1] + dv[1],
+                                  a2H * dx[2] + dv[2]};
+
+  /* Compute the physical relative velocity between the particles */
+  const float dv_physical[3] = {v_plus_H_flow[0] * cosmo->a_inv,
+                                v_plus_H_flow[1] * cosmo->a_inv,
+                                v_plus_H_flow[2] * cosmo->a_inv};
+
+  /* Collect the swallowed angular momentum */
+  sp->swallowed_angular_momentum[0] +=
+      gas_mass *
+      (dx_physical[1] * dv_physical[2] - dx_physical[2] * dv_physical[1]);
+  sp->swallowed_angular_momentum[1] +=
+      gas_mass *
+      (dx_physical[2] * dv_physical[0] - dx_physical[0] * dv_physical[2]);
+  sp->swallowed_angular_momentum[2] +=
+      gas_mass *
+      (dx_physical[0] * dv_physical[1] - dx_physical[1] * dv_physical[0]);
+
+  /* Update the sink momentum */
+  const float sink_mom[3] = {sink_mass * sp->v[0] + gas_mass * p->v[0],
+                             sink_mass * sp->v[1] + gas_mass * p->v[1],
+                             sink_mass * sp->v[2] + gas_mass * p->v[2]};
+
+  sp->v[0] = sink_mom[0] / sp->mass;
+  sp->v[1] = sink_mom[1] / sp->mass;
+  sp->v[2] = sink_mom[2] / sp->mass;
+  sp->gpart->v_full[0] = sp->v[0];
+  sp->gpart->v_full[1] = sp->v[1];
+  sp->gpart->v_full[2] = sp->v[2];
+
+  /* This sink swallowed a gas particle */
+  sp->number_of_gas_swallows++;
+  sp->number_of_direct_gas_swallows++;
+
+#ifdef SWIFT_DEBUG_CHECKS
+  const float dr = sqrt(dx[0] * dx[0] + dx[1] * dx[1] + dx[2] * dx[2]);
+  message(
+      "sink %lld swallow gas particle %lld. "
+      "(Mass = %e, "
+      "Delta_v = [%f, %f, %f] U_V, "
+      "Delta_x = [%f, %f, %f] U_L, "
+      "Delta_v_rad = %f)",
+      sp->id, p->id, sp->mass, -dv[0], -dv[1], -dv[2], -dx[0], -dx[1], -dx[2],
+      (dv[0] * dx[0] + dv[1] * dx[1] + dv[2] * dx[2]) / dr);
+#endif
+}
+
+/**
+ * @brief Update the properties of a sink particles by swallowing
+ * a sink particle.
+ *
+ * @param spi The #sink to update.
+ * @param spj The #sink that is swallowed.
+ * @param cosmo The current cosmological model.
+ */
+__attribute__((always_inline)) INLINE static void sink_swallow_sink(
+    struct sink* spi, const struct sink* spj, const struct cosmology* cosmo) {
+
+  /* Get the current dynamical masses */
+  const float spi_dyn_mass = spi->mass;
+  const float spj_dyn_mass = spj->mass;
+
+  /* Increase the masses of the sink. */
+  spi->mass += spj->mass;
+  spi->gpart->mass += spj->mass;
+  spi->subgrid_mass += spj->subgrid_mass;
+
+  /* Collect the swallowed angular momentum */
+  spi->swallowed_angular_momentum[0] += spj->swallowed_angular_momentum[0];
+  spi->swallowed_angular_momentum[1] += spj->swallowed_angular_momentum[1];
+  spi->swallowed_angular_momentum[2] += spj->swallowed_angular_momentum[2];
+
+  /* Update the sink momentum */
+  const float sink_mom[3] = {
+      spi_dyn_mass * spi->v[0] + spj_dyn_mass * spj->v[0],
+      spi_dyn_mass * spi->v[1] + spj_dyn_mass * spj->v[1],
+      spi_dyn_mass * spi->v[2] + spj_dyn_mass * spj->v[2]};
+
+  spi->v[0] = sink_mom[0] / spi->mass;
+  spi->v[1] = sink_mom[1] / spi->mass;
+  spi->v[2] = sink_mom[2] / spi->mass;
+  spi->gpart->v_full[0] = spi->v[0];
+  spi->gpart->v_full[1] = spi->v[1];
+  spi->gpart->v_full[2] = spi->v[2];
+
+  /* This sink swallowed a sink particle */
+  spi->number_of_sink_swallows++;
+  spi->number_of_direct_sink_swallows++;
+
+  /* Add all other swallowed particles swallowed by the swallowed sink */
+  spi->number_of_sink_swallows += spj->number_of_sink_swallows;
+  spi->number_of_gas_swallows += spj->number_of_gas_swallows;
+
+  message("sink %lld swallow sink particle %lld. New mass: %e.", spi->id,
+          spj->id, spi->mass);
+}
+
+/**
+ * @brief Should the sink spawn a star particle?
+ *
+ * @param sink the sink particle.
+ * @param e The #engine
+ * @param sink_props The sink properties to use.
+ * @param cosmo The cosmological parameters and properties.
+ * @param with_cosmology If we run with cosmology.
+ * @param phys_const The physical constants in internal units.
+ * @param us The internal unit system.
+ */
+INLINE static int sink_spawn_star(struct sink* sink, const struct engine* e,
+                                  const struct sink_props* sink_props,
+                                  const struct cosmology* cosmo,
+                                  const int with_cosmology,
+                                  const struct phys_const* phys_const,
+                                  const struct unit_system* restrict us) {
+
+  /* Star formation from sinks is disabled in this model. */
+  return 0;
+}
+
+/**
+ * @brief Copy the properties of the sink particle towards the new star. Also,
+ * give the stars some properties such as position and velocity.
+ *
+ * This function also needs to update the sink particle.
+ *
+ * @param sink The #sink particle.
+ * @param sp The star particle.
+ * @param e The #engine
+ * @param sink_props The sink properties to use.
+ * @param cosmo The cosmological parameters and properties.
+ * @param with_cosmology If we run with cosmology.
+ * @param phys_const The physical constants in internal units.
+ * @param us The internal unit system.
+ */
+INLINE static void sink_copy_properties_to_star(
+    struct sink* sink, struct spart* sp, const struct engine* e,
+    const struct sink_props* sink_props, const struct cosmology* cosmo,
+    const int with_cosmology, const struct phys_const* phys_const,
+    const struct unit_system* restrict us) {}
+
+/**
+ * @brief Update the #sink particle properties before spawning a star.
+ *
+ * In GEAR, we check if the sink had an IMF change from pop III to pop II
+ * during the last gas/sink accretion loops. If so, we draw a new target mass
+ * with the correct IMF so that stars have the right metallicities.
+ *
+ * @param sink The #sink particle.
+ * @param e The #engine
+ * @param sink_props The sink properties to use.
+ * @param phys_const The physical constants in internal units.
+ */
+INLINE static void sink_update_sink_properties_before_star_formation(
+    struct sink* sink, const struct engine* e,
+    const struct sink_props* sink_props, const struct phys_const* phys_const) {}
+
+/**
+ * @brief Update the #sink particle properties right after spawning a star.
+ *
+ * In GEAR: Important properties that are updated are the sink mass and the
+ * sink->target_mass_Msun to draw the next star mass.
+ *
+ * @param sink The #sink particle that spawed stars.
+ * @param sp The #spart particle spawned.
+ * @param e The #engine
+ * @param sink_props the sink properties to use.
+ * @param phys_const the physical constants in internal units.
+ * @param star_counter The star loop counter.
+ */
+INLINE static void sink_update_sink_properties_during_star_formation(
+    struct sink* sink, const struct spart* sp, const struct engine* e,
+    const struct sink_props* sink_props, const struct phys_const* phys_const,
+    int star_counter) {}
+
+/**
+ * @brief Update the #sink particle properties after star formation.
+ *
+ * In GEAR, this is unused.
+ *
+ * @param sink The #sink particle.
+ * @param e The #engine
+ * @param sink_props The sink properties to use.
+ * @param phys_const The physical constants in internal units.
+ */
+INLINE static void sink_update_sink_properties_after_star_formation(
+    struct sink* sink, const struct engine* e,
+    const struct sink_props* sink_props, const struct phys_const* phys_const) {}
+
+/**
+ * @brief Store the gravitational potential of a particle by copying it from
+ * its #gpart friend.
+ *
+ * @param p_data The sink data of a gas particle.
+ * @param gp The part's #gpart.
+ */
+__attribute__((always_inline)) INLINE static void sink_store_potential_in_part(
+    struct sink_part_data* p_data, const struct gpart* gp) {}
+
+/**
+ * @brief Compute all quantities required for the formation of a sink such as
+ * kinetic energy, potential energy, etc. This function works on the
+ * neighbouring gas particles.
+ *
+ * @param e The #engine.
+ * @param p The #part for which we compute the quantities.
+ * @param xp The #xpart data of the particle #p.
+ * @param pi A neighbouring #part of #p.
+ * @param xpi The #xpart data of the particle #pi.
+ * @param cosmo The cosmological parameters and properties.
+ * @param sink_props The sink properties to use.
+ */
+INLINE static void sink_prepare_part_sink_formation_gas_criteria(
+    struct engine* e, struct part* restrict p, struct xpart* restrict xp,
+    struct part* restrict pi, struct xpart* restrict xpi,
+    const struct cosmology* cosmo, const struct sink_props* sink_props) {}
+
+/**
+ * @brief Compute all quantities required for the formation of a sink. This
+ * function works on the neighbouring sink particles.
+ *
+ * @param e The #engine.
+ * @param p The #part for which we compute the quantities.
+ * @param xp The #xpart data of the particle #p.
+ * @param si A neighbouring #sink of #p.
+ * @param cosmo The cosmological parameters and properties.
+ * @param sink_props The sink properties to use.
+ */
+INLINE static void sink_prepare_part_sink_formation_sink_criteria(
+    struct engine* e, struct part* restrict p, struct xpart* restrict xp,
+    struct sink* restrict si, const int with_cosmology,
+    const struct cosmology* cosmo, const struct sink_props* sink_props,
+    const double time) {}
+
+#endif /* SWIFT_BASIC_SINK_H */
diff --git a/src/sink/Basic/sink_debug.h b/src/sink/Basic/sink_debug.h
new file mode 100644
index 0000000000000000000000000000000000000000..441af55c3ecf4083b5c5cb3b563b706e13ea59ac
--- /dev/null
+++ b/src/sink/Basic/sink_debug.h
@@ -0,0 +1,29 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2024 Jonathan Davies (j.j.davies@ljmu.ac.uk)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ ******************************************************************************/
+#ifndef SWIFT_SINK_BASIC_DEBUG_H
+#define SWIFT_SINK_BASIC_DEBUG_H
+
+__attribute__((always_inline)) INLINE static void sink_debug_particle(
+    const struct part* p, const struct xpart* xp) {
+
+  warning("[PID%lld] sink_part_data:", p->id);
+  warning("[PID%lld] swallow_id = %lld", p->id, p->sink_data.swallow_id);
+}
+
+#endif /* SWIFT_SINK_BASIC_DEBUG_H */
diff --git a/src/sink/Basic/sink_iact.h b/src/sink/Basic/sink_iact.h
new file mode 100644
index 0000000000000000000000000000000000000000..f6cdf999dd31efc31bce9e954503a854bb1771fc
--- /dev/null
+++ b/src/sink/Basic/sink_iact.h
@@ -0,0 +1,373 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2024 Jonathan Davies (j.j.davies@ljmu.ac.uk)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ ******************************************************************************/
+#ifndef SWIFT_BASIC_SINKS_IACT_H
+#define SWIFT_BASIC_SINKS_IACT_H
+
+/* Local includes */
+#include "gravity.h"
+#include "gravity_iact.h"
+#include "random.h"
+#include "sink_properties.h"
+
+/**
+ * @brief Gas particle interactions relevant for sinks, to run in hydro density
+ * interaction
+ *
+ * @param r2 Comoving square distance between the two particles.
+ * @param dx Comoving vector separating both particles (pi - pj).
+ * @param hi Comoving smoothing-length of particle i.
+ * @param hj Comoving smoothing-length of particle j.
+ * @param pi First particle.
+ * @param pj Second particle.
+ * @param a Current scale factor.
+ * @param H Current Hubble parameter.
+ */
+__attribute__((always_inline)) INLINE static void runner_iact_sink(
+    const float r2, const float dx[3], const float hi, const float hj,
+    struct part *restrict pi, struct part *restrict pj, const float a,
+    const float H) {}
+
+/**
+ * @brief Gas particle interactions relevant for sinks, to run in hydro density
+ * interaction (non symmetric version)
+ *
+ * @param r2 Comoving square distance between the two particles.
+ * @param dx Comoving vector separating both particles (pi - pj).
+ * @param hi Comoving smoothing-length of particle i.
+ * @param hj Comoving smoothing-length of particle j.
+ * @param pi First particle.
+ * @param pj Second particle (not updated).
+ * @param a Current scale factor.
+ * @param H Current Hubble parameter.
+ */
+__attribute__((always_inline)) INLINE static void runner_iact_nonsym_sink(
+    const float r2, const float dx[3], const float hi, const float hj,
+    struct part *restrict pi, const struct part *restrict pj, const float a,
+    const float H) {}
+
+/**
+ * @brief Density interaction between sinks and gas (non-symmetric).
+ *
+ * The gas particle cannot be touched.
+ *
+ * @param r2 Comoving square distance between the two particles.
+ * @param dx Comoving vector separating both particles (pi - pj).
+ * @param hi Comoving smoothing length or cut off radius of particle i.
+ * @param hj Comoving smoothing-length of particle j.
+ * @param si First particle (sink).
+ * @param pj Second particle (gas, not updated).
+ * @param with_cosmology Are we doing a cosmological run?
+ * @param cosmo The cosmological model.
+ * @param grav_props The properties of the gravity scheme (softening, G, ...).
+ * @param sink_props the sink properties to use.
+ * @param ti_current Current integer time value (for random numbers).
+ * @param time current physical time in the simulation
+ */
+__attribute__((always_inline)) INLINE static void
+runner_iact_nonsym_sinks_gas_density(
+    const float r2, const float dx[3], const float hi, const float hj,
+    struct sink *si, const struct part *pj, const int with_cosmology,
+    const struct cosmology *cosmo, const struct gravity_props *grav_props,
+    const struct sink_props *sink_props, const integertime_t ti_current,
+    const double time) {
+
+  float wi, wi_dx;
+
+  /* Get r. */
+  const float r = sqrtf(r2);
+
+  /* Compute the kernel function */
+  const float hi_inv = 1.0f / hi;
+  const float ui = r * hi_inv;
+  kernel_deval(ui, &wi, &wi_dx);
+
+  /* Compute contribution to the number of neighbours */
+  si->density.wcount += wi;
+  si->density.wcount_dh -= (hydro_dimension * wi + ui * wi_dx);
+
+  /* Contribution to the number of neighbours */
+  si->num_ngbs++;
+
+  /* Neighbour gas mass */
+  const float mj = hydro_get_mass(pj);
+
+  /* Contribution to the BH gas density */
+  si->rho_gas += mj * wi;
+
+  /* Contribution to the total neighbour mass */
+  si->ngb_mass += mj;
+
+  /* Neighbour's sound speed */
+  const float cj = hydro_get_comoving_soundspeed(pj);
+
+  /* Contribution to the smoothed sound speed */
+  si->sound_speed_gas += mj * cj * wi;
+
+  /* Neighbour's (drifted) velocity in the frame of the black hole
+   * (we do include a Hubble term) */
+  const float dv[3] = {pj->v[0] - si->v[0], pj->v[1] - si->v[1],
+                       pj->v[2] - si->v[2]};
+
+  /* Contribution to the smoothed velocity (gas w.r.t. black hole) */
+  si->velocity_gas[0] += mj * dv[0] * wi;
+  si->velocity_gas[1] += mj * dv[1] * wi;
+  si->velocity_gas[2] += mj * dv[2] * wi;
+
+#ifdef SWIFT_SINK_DENSITY_CHECKS
+  si->rho_check += mj * wi;
+  si->n_check += wi;
+  si->N_check_density++;
+#endif
+}
+
+/**
+ * @brief Compute sink-sink swallow interaction (non-symmetric).
+ *
+ * Note: Energies are computed with physical quantities, not the comoving ones.
+ *
+ * @param r2 Comoving square distance between the two particles.
+ * @param dx Comoving vector separating both particles (pi - pj).
+ * @param hi Comoving smoothing length or cut off radius of particle i.
+ * @param hj Comoving smoothing length or cut off radius of particle j.
+ * @param si First sink particle.
+ * @param sj Second sink particle.
+ * @param with_cosmology if we run with cosmology.
+ * @param cosmo The cosmological parameters and properties.
+ * @param grav_props The gravity scheme parameters and properties.
+ * @param sink_props the sink properties to use.
+ * @param ti_current Current integer time value (for random numbers).
+ * @param time current physical time in the simulation
+ */
+__attribute__((always_inline)) INLINE static void
+runner_iact_nonsym_sinks_sink_swallow(
+    const float r2, const float dx[3], const float hi, const float hj,
+    struct sink *restrict si, struct sink *restrict sj,
+    const int with_cosmology, const struct cosmology *cosmo,
+    const struct gravity_props *grav_props,
+    const struct sink_props *sink_properties, const integertime_t ti_current,
+    const double time) {
+
+  /* Simpler version of GEAR as a placeholder. Sinks bound to each other are
+   * merged. */
+
+  /* Relative velocity between the sinks */
+  const float dv[3] = {sj->v[0] - si->v[0], sj->v[1] - si->v[1],
+                       sj->v[2] - si->v[2]};
+
+  const float a = cosmo->a;
+  const float H = cosmo->H;
+  const float a2H = a * a * H;
+
+  /* Calculate the velocity with the Hubble flow */
+  const float v_plus_H_flow[3] = {a2H * dx[0] + dv[0], a2H * dx[1] + dv[1],
+                                  a2H * dx[2] + dv[2]};
+
+  /* Binding energy check */
+  /* Compute the physical relative velocity between the particles */
+  const float dv_physical[3] = {v_plus_H_flow[0] * cosmo->a_inv,
+                                v_plus_H_flow[1] * cosmo->a_inv,
+                                v_plus_H_flow[2] * cosmo->a_inv};
+
+  const float dv_physical_squared = dv_physical[0] * dv_physical[0] +
+                                    dv_physical[1] * dv_physical[1] +
+                                    dv_physical[2] * dv_physical[2];
+
+  /* Kinetic energy per unit mass of the gas */
+  const float E_kin_rel = 0.5f * dv_physical_squared;
+
+  /* Compute the Newtonian or softened potential the sink exherts onto the
+      gas particle */
+  /* TODO: needs updating for MPI safety. We don't have access to foreign gparts
+   * here. */
+  const float eps = gravity_get_softening(si->gpart, grav_props);
+  const float eps2 = eps * eps;
+  const float eps_inv = 1.f / eps;
+  const float eps_inv3 = eps_inv * eps_inv * eps_inv;
+  const float si_mass = si->mass;
+  const float sj_mass = sj->mass;
+
+  float dummy, pot_ij, pot_ji;
+  runner_iact_grav_pp_full(r2, eps2, eps_inv, eps_inv3, si_mass, &dummy,
+                           &pot_ij);
+  runner_iact_grav_pp_full(r2, eps2, eps_inv, eps_inv3, sj_mass, &dummy,
+                           &pot_ji);
+
+  /* Compute the physical potential energies per unit mass :
+                          E_pot_phys = G*pot_grav*a^(-1) + c(a).
+      The normalization is c(a) = 0. */
+  const float E_pot_ij = grav_props->G_Newton * pot_ij * cosmo->a_inv;
+  const float E_pot_ji = grav_props->G_Newton * pot_ji * cosmo->a_inv;
+
+  /* Mechanical energy per unit mass of the pair i-j and j-i */
+  const float E_mec_si = E_kin_rel + E_pot_ij;
+  const float E_mec_sj = E_kin_rel + E_pot_ji;
+
+  /* Now, check if one is bound to the other */
+  if ((E_mec_si > 0) || (E_mec_sj > 0)) {
+    return;
+  }
+
+  /* The sink with the smaller mass will be merged onto the one with the
+   * larger mass.
+   * To avoid rounding issues, we additionally check for IDs if the sink
+   * have the exact same mass. */
+  if ((sj->mass < si->mass) || (sj->mass == si->mass && sj->id < si->id)) {
+    /* This particle is swallowed by the sink with the largest mass of all the
+     * candidates wanting to swallow it (we use IDs to break ties)*/
+    if ((sj->merger_data.swallow_mass < si->mass) ||
+        (sj->merger_data.swallow_mass == si->mass &&
+         sj->merger_data.swallow_id < si->id)) {
+      sj->merger_data.swallow_id = si->id;
+      sj->merger_data.swallow_mass = si->mass;
+    }
+  }
+
+#ifdef DEBUG_INTERACTIONS_SINKS
+  /* Update ngb counters */
+  if (si->num_ngb_formation < MAX_NUM_OF_NEIGHBOURS_SINKS)
+    si->ids_ngbs_formation[si->num_ngb_formation] = pj->id;
+
+  /* Update ngb counters */
+  ++si->num_ngb_formation;
+#endif
+}
+
+/**
+ * @brief Compute sink-gas swallow interaction (non-symmetric).
+ *
+ * Note: Energies are computed with physical quantities, not the comoving ones.
+ *
+ * @param r2 Comoving square distance between the two particles.
+ * @param dx Comoving vector separating both particles (pi - pj).
+ * @param hi Comoving smoothing length or cut off radius of particle i.
+ * @param hj Comoving smoothing-length of particle j.
+ * @param si First sink particle.
+ * @param pj Second particle.
+ * @param ti_current Current integer time value (for random numbers).
+ * @param time current physical time in the simulation
+ */
+__attribute__((always_inline)) INLINE static void
+runner_iact_nonsym_sinks_gas_swallow(
+    const float r2, const float dx[3], const float hi, const float hj,
+    struct sink *restrict si, struct part *restrict pj,
+    const int with_cosmology, const struct cosmology *cosmo,
+    const struct gravity_props *grav_props,
+    const struct sink_props *sink_properties, const integertime_t ti_current,
+    const double time) {
+
+  float wi;
+
+  /* Get r. */
+  const float r = sqrtf(r2);
+
+  /* Compute the kernel function */
+  const float hi_inv = 1.0f / hi;
+  const float hi_inv_dim = pow_dimension(hi_inv);
+  const float ui = r * hi_inv;
+  kernel_eval(ui, &wi);
+
+  /* Check if the sink needs to be fed. If not, we're done here */
+  const float sink_mass_deficit = si->subgrid_mass - si->mass_at_start_of_step;
+  if (sink_mass_deficit <= 0) return;
+
+  if (sink_properties->use_nibbling) {
+
+    /* If we do nibbling, things are quite straightforward. We transfer
+     * the mass and all associated quantities right here. */
+
+    const float si_mass_orig = si->mass;
+    const float pj_mass_orig = hydro_get_mass(pj);
+
+    /* Don't nibble from particles that are too small already */
+    if (pj_mass_orig < sink_properties->min_gas_mass_for_nibbling) return;
+
+    /* Next line is equivalent to w_ij * m_j / Sum_j (w_ij * m_j) */
+    const float particle_weight = hi_inv_dim * wi * pj_mass_orig / si->rho_gas;
+    float nibble_mass = sink_mass_deficit * particle_weight;
+
+    /* Need to check whether nibbling would push gas mass below minimum
+     * allowed mass */
+    float new_gas_mass = pj_mass_orig - nibble_mass;
+    if (new_gas_mass < sink_properties->min_gas_mass_for_nibbling) {
+      new_gas_mass = sink_properties->min_gas_mass_for_nibbling;
+      nibble_mass = pj_mass_orig - sink_properties->min_gas_mass_for_nibbling;
+    }
+
+    /* Transfer (dynamical) mass from the gas particle to the sink */
+    si->mass += nibble_mass;
+    hydro_set_mass(pj, new_gas_mass);
+
+    /* Add the angular momentum of the accreted gas to the sink total.
+     * Note no change to gas here. The cosmological conversion factors for
+     * velocity (a^-1) and distance (a) cancel out, so the angular momentum
+     * is already in physical units. */
+    const float dv[3] = {si->v[0] - pj->v[0], si->v[1] - pj->v[1],
+                         si->v[2] - pj->v[2]};
+    si->swallowed_angular_momentum[0] +=
+        nibble_mass * (dx[1] * dv[2] - dx[2] * dv[1]);
+    si->swallowed_angular_momentum[1] +=
+        nibble_mass * (dx[2] * dv[0] - dx[0] * dv[2]);
+    si->swallowed_angular_momentum[2] +=
+        nibble_mass * (dx[0] * dv[1] - dx[1] * dv[0]);
+
+    /* Update the BH momentum and velocity. Again, no change to gas here. */
+    const float si_mom[3] = {si_mass_orig * si->v[0] + nibble_mass * pj->v[0],
+                             si_mass_orig * si->v[1] + nibble_mass * pj->v[1],
+                             si_mass_orig * si->v[2] + nibble_mass * pj->v[2]};
+
+    si->v[0] = si_mom[0] / si->mass;
+    si->v[1] = si_mom[1] / si->mass;
+    si->v[2] = si_mom[2] / si->mass;
+
+  } else { /* ends nibbling section, below comes swallowing */
+
+    /* Probability to swallow this particle
+     * Recall that in SWIFT the SPH kernel is recovered by computing
+     * kernel_eval() and muliplying by (1/h^d) */
+
+    const float prob =
+        (si->subgrid_mass - si->mass) * hi_inv_dim * wi / si->rho_gas;
+
+    /* Draw a random number (Note mixing both IDs) */
+    const float rand = random_unit_interval_two_IDs(si->id, pj->id, ti_current,
+                                                    random_number_sink_swallow);
+
+    /* Are we lucky? */
+    if (rand < prob) {
+
+      /* This particle is swallowed by the BH with the largest ID of all the
+       * candidates wanting to swallow it */
+      if (pj->sink_data.swallow_id < si->id) {
+
+        message("Sink %lld wants to swallow gas particle %lld", si->id, pj->id);
+
+        pj->sink_data.swallow_id = si->id;
+
+      } else {
+
+        message(
+            "Sink %lld wants to swallow gas particle %lld BUT CANNOT (old "
+            "swallow id=%lld)",
+            si->id, pj->id, pj->sink_data.swallow_id);
+      }
+    }
+  } /* ends section for swallowing */
+}
+
+#endif
diff --git a/src/sink/Basic/sink_io.h b/src/sink/Basic/sink_io.h
new file mode 100644
index 0000000000000000000000000000000000000000..0e0cabc6958a47adb1d83bb79f612302b3d3eea3
--- /dev/null
+++ b/src/sink/Basic/sink_io.h
@@ -0,0 +1,219 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2024 Jonathan Davies (j.j.davies@ljmu.ac.uk)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ ******************************************************************************/
+#ifndef SWIFT_BASIC_SINK_IO_H
+#define SWIFT_BASIC_SINK_IO_H
+
+#include "io_properties.h"
+#include "sink_part.h"
+
+/**
+ * @brief Specifies which sink-particle fields to read from a dataset
+ *
+ * @param sinks The sink-particle array.
+ * @param list The list of i/o properties to read.
+ * @param num_fields The number of i/o fields to read.
+ */
+INLINE static void sink_read_particles(struct sink* sinks,
+                                       struct io_props* list, int* num_fields) {
+
+  /* Say how much we want to read */
+  *num_fields = 5;
+
+  /* List what we want to read */
+  list[0] = io_make_input_field("Coordinates", DOUBLE, 3, COMPULSORY,
+                                UNIT_CONV_LENGTH, sinks, x);
+  list[1] = io_make_input_field("Velocities", FLOAT, 3, COMPULSORY,
+                                UNIT_CONV_SPEED, sinks, v);
+  list[2] = io_make_input_field("Masses", FLOAT, 1, COMPULSORY, UNIT_CONV_MASS,
+                                sinks, mass);
+  list[3] = io_make_input_field("ParticleIDs", LONGLONG, 1, COMPULSORY,
+                                UNIT_CONV_NO_UNITS, sinks, id);
+  list[4] = io_make_input_field("SmoothingLength", FLOAT, 1, OPTIONAL,
+                                UNIT_CONV_LENGTH, sinks, h);
+}
+
+INLINE static void convert_sink_pos(const struct engine* e,
+                                    const struct sink* sp, double* ret) {
+
+  const struct space* s = e->s;
+  if (s->periodic) {
+    ret[0] = box_wrap(sp->x[0], 0.0, s->dim[0]);
+    ret[1] = box_wrap(sp->x[1], 0.0, s->dim[1]);
+    ret[2] = box_wrap(sp->x[2], 0.0, s->dim[2]);
+  } else {
+    ret[0] = sp->x[0];
+    ret[1] = sp->x[1];
+    ret[2] = sp->x[2];
+  }
+}
+
+INLINE static void convert_sink_vel(const struct engine* e,
+                                    const struct sink* sp, float* ret) {
+
+  const int with_cosmology = (e->policy & engine_policy_cosmology);
+  const struct cosmology* cosmo = e->cosmology;
+  const integertime_t ti_current = e->ti_current;
+  const double time_base = e->time_base;
+
+  const integertime_t ti_beg = get_integer_time_begin(ti_current, sp->time_bin);
+  const integertime_t ti_end = get_integer_time_end(ti_current, sp->time_bin);
+
+  /* Get time-step since the last kick */
+  float dt_kick_grav;
+  if (with_cosmology) {
+    dt_kick_grav = cosmology_get_grav_kick_factor(cosmo, ti_beg, ti_current);
+    dt_kick_grav -=
+        cosmology_get_grav_kick_factor(cosmo, ti_beg, (ti_beg + ti_end) / 2);
+  } else {
+    dt_kick_grav = (ti_current - ((ti_beg + ti_end) / 2)) * time_base;
+  }
+
+  /* Extrapolate the velocites to the current time */
+  const struct gpart* gp = sp->gpart;
+  ret[0] = gp->v_full[0] + gp->a_grav[0] * dt_kick_grav;
+  ret[1] = gp->v_full[1] + gp->a_grav[1] * dt_kick_grav;
+  ret[2] = gp->v_full[2] + gp->a_grav[2] * dt_kick_grav;
+
+  /* Conversion from internal units to peculiar velocities */
+  ret[0] *= cosmo->a_inv;
+  ret[1] *= cosmo->a_inv;
+  ret[2] *= cosmo->a_inv;
+}
+
+INLINE static void convert_sink_gas_vel(const struct engine* e,
+                                        const struct sink* sink, float* ret) {
+  const struct cosmology* cosmo = e->cosmology;
+  ret[0] = sink->velocity_gas[0] * cosmo->a_inv;
+  ret[1] = sink->velocity_gas[1] * cosmo->a_inv;
+  ret[2] = sink->velocity_gas[2] * cosmo->a_inv;
+}
+
+INLINE static void convert_sink_gas_sound_speed(const struct engine* e,
+                                                const struct sink* sink,
+                                                double* ret) {
+  const struct cosmology* cosmo = e->cosmology;
+  ret[0] = sink->sound_speed_gas * cosmo->a_factor_sound_speed;
+}
+
+INLINE static void convert_sink_swallowed_angular_momentum(
+    const struct engine* e, const struct sink* sink, float* ret) {
+  ret[0] = sink->swallowed_angular_momentum[0];
+  ret[1] = sink->swallowed_angular_momentum[1];
+  ret[2] = sink->swallowed_angular_momentum[2];
+}
+
+/**
+ * @brief Specifies which sink-particle fields to write to a dataset
+ *
+ * @param sinks The sink-particle array.
+ * @param list The list of i/o properties to write.
+ * @param num_fields The number of i/o fields to write.
+ * @param with_cosmology Are we running a cosmological simulation?
+ */
+INLINE static void sink_write_particles(const struct sink* sinks,
+                                        struct io_props* list, int* num_fields,
+                                        int with_cosmology) {
+
+  /* Say how much we want to write */
+  *num_fields = 12;
+
+  /* List what we want to write */
+  list[0] = io_make_output_field_convert_sink(
+      "Coordinates", DOUBLE, 3, UNIT_CONV_LENGTH, 1.f, sinks, convert_sink_pos,
+      "Co-moving position of the particles");
+
+  list[1] = io_make_output_field_convert_sink(
+      "Velocities", FLOAT, 3, UNIT_CONV_SPEED, 0.f, sinks, convert_sink_vel,
+      "Peculiar velocities of the particles. This is a * dx/dt where x is the "
+      "co-moving position of the particles.");
+
+  list[2] = io_make_output_field("Masses", FLOAT, 1, UNIT_CONV_MASS, 0.f, sinks,
+                                 mass, "Dynamical masses of the sinks.");
+
+  list[3] = io_make_physical_output_field(
+      "ParticleIDs", ULONGLONG, 1, UNIT_CONV_NO_UNITS, 0.f, sinks, id,
+      /*can convert to comoving=*/0, "Unique ID of the particles");
+
+  list[4] = io_make_output_field(
+      "SmoothingLengths", FLOAT, 1, UNIT_CONV_LENGTH, 1.f, sinks, h,
+      "Co-moving smoothing lengths (FWHM of the kernel) of the particles");
+
+  list[5] = io_make_physical_output_field(
+      "NumberOfSinkSwallows", INT, 1, UNIT_CONV_NO_UNITS, 0.f, sinks,
+      number_of_sink_swallows, /*can convert to comoving=*/0,
+      "Total number of sink merger events");
+
+  list[6] = io_make_physical_output_field(
+      "NumberOfGasSwallows", INT, 1, UNIT_CONV_NO_UNITS, 0.f, sinks,
+      number_of_gas_swallows, /*can convert to comoving=*/0,
+      "Total number of gas merger events");
+
+  /* Note: Since the swallowed momentum is computed with the physical velocity,
+     i.e. including the Hubble flow term, it is not convertible to comoving
+     frame. */
+  list[7] = io_make_physical_output_field_convert_sink(
+      "SwallowedAngularMomentum", FLOAT, 3, UNIT_CONV_ANGULAR_MOMENTUM, 0.f,
+      sinks,
+      /*can convert to comoving=*/0, convert_sink_swallowed_angular_momentum,
+      "Physical swallowed angular momentum of the particles");
+
+  list[8] = io_make_output_field(
+      "SubgridMasses", FLOAT, 1, UNIT_CONV_MASS, 0.f, sinks, subgrid_mass,
+      "Subgrid mass of the sink. Summed on sink-sink mergers");
+
+  list[9] = io_make_physical_output_field(
+      "GasDensities", FLOAT, 1, UNIT_CONV_DENSITY, -3.f, sinks, rho_gas,
+      /*can convert to comoving=*/1, "Gas density at the location of the sink");
+
+  list[10] = io_make_output_field_convert_sink(
+      "GasVelocities", FLOAT, 3, UNIT_CONV_SPEED, 0.f, sinks,
+      convert_sink_gas_vel,
+      "Gas velocity at the location of the sink. Velocities are peculiar, i.e. "
+      "a * dx/dt where x is the "
+      "co-moving position of the gas.");
+
+  list[11] = io_make_output_field_convert_sink(
+      "GasSoundSpeeds", DOUBLE, 1, UNIT_CONV_SPEED, 0.f, sinks,
+      convert_sink_gas_sound_speed,
+      "Gas sound speed at the location of the sink (physical units)");
+
+#ifdef DEBUG_INTERACTIONS_SINKS
+
+  list += *num_fields;
+  *num_fields += 4;
+
+  list[0] =
+      io_make_output_field("Num_ngb_formation", INT, 1, UNIT_CONV_NO_UNITS, 0.f,
+                           sinks, num_ngb_formation, "Number of neighbors");
+  list[1] =
+      io_make_output_field("Ids_ngb_formation", LONGLONG,
+                           MAX_NUM_OF_NEIGHBOURS_SINKS, UNIT_CONV_NO_UNITS, 0.f,
+                           sinks, ids_ngbs_formation, "IDs of the neighbors");
+
+  list[2] =
+      io_make_output_field("Num_ngb_merger", INT, 1, UNIT_CONV_NO_UNITS, 0.f,
+                           sinks, num_ngb_merger, "Number of merger");
+  list[3] = io_make_output_field(
+      "Ids_ngb_merger", LONGLONG, MAX_NUM_OF_NEIGHBOURS_SINKS,
+      UNIT_CONV_NO_UNITS, 0.f, sinks, ids_ngbs_merger, "IDs of the neighbors");
+
+#endif
+}
+
+#endif /* SWIFT_BASIC_SINK_IO_H */
diff --git a/src/sink/Basic/sink_part.h b/src/sink/Basic/sink_part.h
new file mode 100644
index 0000000000000000000000000000000000000000..9e1ccbbe85910ca788ef73cef379e412e6238719
--- /dev/null
+++ b/src/sink/Basic/sink_part.h
@@ -0,0 +1,168 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2024 Jonathan Davies (j.j.davies@ljmu.ac.uk)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ ******************************************************************************/
+#ifndef SWIFT_BASIC_SINK_PART_H
+#define SWIFT_BASIC_SINK_PART_H
+
+#include "timeline.h"
+
+#define sink_need_unique_id 1
+
+/**
+ * @brief Particle fields for the sink particles.
+ *
+ * All quantities related to gravity are stored in the associate #gpart.
+ */
+struct sink {
+
+  /*! Particle ID. */
+  long long id;
+
+  /*! Pointer to corresponding gravity part. */
+  struct gpart* gpart;
+
+  /*! Particle position. */
+  double x[3];
+
+  /* Offset between current position and position at last tree rebuild. */
+  float x_diff[3];
+
+  /*! Particle velocity. */
+  float v[3];
+
+  /* Particle smoothing length */
+  float h;
+
+  struct {
+
+    /* Number of neighbours. */
+    float wcount;
+
+    /* Number of neighbours spatial derivative. */
+    float wcount_dh;
+
+  } density;
+
+  /*! Sink particle mass */
+  float mass;
+
+  /*! Total mass of the gas neighbours. */
+  float ngb_mass;
+
+  /*! Integer number of neighbours */
+  int num_ngbs;
+
+  /*! Instantaneous accretion rate */
+  float accretion_rate;
+
+  /*! Subgrid mass of the sink */
+  float subgrid_mass;
+
+  /*! Sink mass at the start of each step, prior to any nibbling */
+  float mass_at_start_of_step;
+
+  /*! Density of the gas surrounding the black hole. */
+  float rho_gas;
+
+  /*! Smoothed sound speed of the gas surrounding the black hole. */
+  float sound_speed_gas;
+
+  /*! Smoothed velocity of the gas surrounding the black hole,
+   * in the frame of the black hole (internal units) */
+  float velocity_gas[3];
+
+  /*! Particle time bin */
+  timebin_t time_bin;
+
+  /*! Total (physical) angular momentum accumulated by swallowing particles */
+  float swallowed_angular_momentum[3];
+
+  /*! Total number of sink merger events (including sink swallowed
+   * by merged-in sinks) */
+  int number_of_sink_swallows;
+
+  /*! Total number of sink merger events (excluding sink swallowed
+   * by merged-in sinks) */
+  int number_of_direct_sink_swallows;
+
+  /*! Total number of gas particles swallowed (including particles swallowed
+   * by merged-in sinks) */
+  int number_of_gas_swallows;
+
+  /*! Total number of gas particles swallowed (excluding particles swallowed
+   * by merged-in sinks) */
+  int number_of_direct_gas_swallows;
+
+  /*! sink merger information (e.g. merging ID) */
+  struct sink_sink_data merger_data;
+
+#ifdef SWIFT_DEBUG_CHECKS
+
+  /* Time of the last drift */
+  integertime_t ti_drift;
+
+  /* Time of the last kick */
+  integertime_t ti_kick;
+
+#endif
+
+#ifdef DEBUG_INTERACTIONS_SINKS
+  /*! Number of interactions in merger SELF and PAIR */
+  int num_ngb_merger;
+
+  /*! List of interacting particles in merger SELF and PAIR */
+  long long ids_ngbs_merger[MAX_NUM_OF_NEIGHBOURS_SINKS];
+
+  /*! Number of interactions in compute formation SELF and PAIR */
+  int num_ngb_formation;
+
+  /*! List of interacting particles in compute formation SELF and PAIR */
+  long long ids_ngbs_formation[MAX_NUM_OF_NEIGHBOURS_SINKS];
+
+  /*! Number of interactions in compute formation SELF and PAIR */
+  int num_ngb_accretion;
+
+  /*! List of interacting particles in compute formation SELF and PAIR */
+  long long ids_ngbs_accretion[MAX_NUM_OF_NEIGHBOURS_SINKS];
+#endif
+
+#ifdef SWIFT_SINK_DENSITY_CHECKS
+
+  /* Integer number of neighbours in the density loop */
+  int N_check_density;
+
+  /* Exact integer number of neighbours in the density loop */
+  int N_check_density_exact;
+
+  /*! Has this particle interacted with any unhibited neighbour? */
+  char inhibited_check_exact;
+
+  float n_check;
+
+  float n_check_exact;
+
+  float rho_check;
+
+  /*! Exact value of the density field obtained via brute-force loop */
+  float rho_check_exact;
+
+#endif
+
+} SWIFT_STRUCT_ALIGN;
+
+#endif /* SWIFT_BASIC_SINK_PART_H */
diff --git a/src/sink/Basic/sink_properties.h b/src/sink/Basic/sink_properties.h
new file mode 100644
index 0000000000000000000000000000000000000000..12a4d21c4023b239eea4bff0c90621fa822d2f8b
--- /dev/null
+++ b/src/sink/Basic/sink_properties.h
@@ -0,0 +1,148 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2024 Jonathan Davies (j.j.davies@ljmu.ac.uk)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ ******************************************************************************/
+#ifndef SWIFT_BASIC_SINK_PROPERTIES_H
+#define SWIFT_BASIC_SINK_PROPERTIES_H
+
+/* Local header */
+#include "feedback_properties.h"
+#include "parser.h"
+
+/**
+ * @brief Properties of sink in the Default model.
+ */
+struct sink_props {
+
+  /* ----- Basic neighbour search properties ------ */
+
+  /*! Resolution parameter */
+  float eta_neighbours;
+
+  /*! Target weightd number of neighbours (for info only)*/
+  float target_neighbours;
+
+  /*! Smoothing length tolerance */
+  float h_tolerance;
+
+  /*! Tolerance on neighbour number  (for info only)*/
+  float delta_neighbours;
+
+  /*! Maximal number of iterations to converge h */
+  int max_smoothing_iterations;
+
+  /*! Maximal change of h over one time-step */
+  float log_max_h_change;
+
+  /*! Are we using a fixed cutoff radius? (all smoothing length calculations are
+   * disabled if so) */
+  char use_fixed_r_cut;
+
+  /* Use nibbling rather than swallowing for gas? */
+  float use_nibbling;
+
+  /* Gas mass below which sinks will not nibble. */
+  float min_gas_mass_for_nibbling;
+};
+
+/**
+ * @brief Initialise the sink properties from the parameter file.
+ *
+ * @param sp The #sink_props.
+ * @param phys_const The physical constants in the internal unit system.
+ * @param us The internal unit system.
+ * @param params The parsed parameters.
+ * @param cosmo The cosmological model.
+ * @param with_feedback Are we running with feedback?
+ */
+INLINE static void sink_props_init(
+    struct sink_props *sp, struct feedback_props *fp,
+    const struct phys_const *phys_const, const struct unit_system *us,
+    struct swift_params *params, const struct hydro_props *hydro_props,
+    const struct cosmology *cosmo, const int with_feedback) {
+
+  /* We don't use a fixed cutoff radius in this model. This property must always
+   * be present, as we use it to skip smoothing length iterations in
+   * runner_ghost if a fixed cutoff is being used. */
+  sp->use_fixed_r_cut = 0;
+
+  /* Read in the basic neighbour search properties or default to the hydro
+     ones if the user did not provide any different values */
+
+  /* Kernel properties */
+  sp->eta_neighbours = parser_get_opt_param_float(
+      params, "BasicSink:resolution_eta", hydro_props->eta_neighbours);
+
+  /* Tolerance for the smoothing length Newton-Raphson scheme */
+  sp->h_tolerance = parser_get_opt_param_float(params, "BasicSink:h_tolerance",
+                                               hydro_props->h_tolerance);
+
+  /* Get derived properties */
+  sp->target_neighbours = pow_dimension(sp->eta_neighbours) * kernel_norm;
+  const float delta_eta = sp->eta_neighbours * (1.f + sp->h_tolerance);
+  sp->delta_neighbours =
+      (pow_dimension(delta_eta) - pow_dimension(sp->eta_neighbours)) *
+      kernel_norm;
+
+  /* Number of iterations to converge h */
+  sp->max_smoothing_iterations =
+      parser_get_opt_param_int(params, "BasicSink:max_ghost_iterations",
+                               hydro_props->max_smoothing_iterations);
+
+  /* Time integration properties */
+  const float max_volume_change =
+      parser_get_opt_param_float(params, "BasicSink:max_volume_change", -1);
+  if (max_volume_change == -1)
+    sp->log_max_h_change = hydro_props->log_max_h_change;
+  else
+    sp->log_max_h_change = logf(powf(max_volume_change, hydro_dimension_inv));
+
+  sp->use_nibbling = parser_get_param_int(params, "BasicSink:use_nibbling");
+  if (sp->use_nibbling) {
+    sp->min_gas_mass_for_nibbling = parser_get_param_float(
+        params, "BasicSink:min_gas_mass_for_nibbling_Msun");
+    sp->min_gas_mass_for_nibbling *= phys_const->const_solar_mass;
+  }
+}
+
+/**
+ * @brief Write a sink_props struct to the given FILE as a stream of
+ * bytes.
+ *
+ * @param props the sink properties struct
+ * @param stream the file stream
+ */
+INLINE static void sink_struct_dump(const struct sink_props *props,
+                                    FILE *stream) {
+  restart_write_blocks((void *)props, sizeof(struct sink_props), 1, stream,
+                       "sink props", "Sink props");
+}
+
+/**
+ * @brief Restore a sink_props struct from the given FILE as a stream of
+ * bytes.
+ *
+ * @param props the sink properties struct
+ * @param stream the file stream
+ */
+INLINE static void sink_struct_restore(const struct sink_props *props,
+                                       FILE *stream) {
+  restart_read_blocks((void *)props, sizeof(struct sink_props), 1, stream, NULL,
+                      "Sink props");
+}
+
+#endif /* SWIFT_BASIC_SINK_PROPERTIES_H */
diff --git a/src/sink/Basic/sink_struct.h b/src/sink/Basic/sink_struct.h
new file mode 100644
index 0000000000000000000000000000000000000000..a25536824314e08071fe2bb394b24448acaf76db
--- /dev/null
+++ b/src/sink/Basic/sink_struct.h
@@ -0,0 +1,115 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2024 Jonathan Davies (j.j.davies@ljmu.ac.uk)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ ******************************************************************************/
+#ifndef SWIFT_BASIC_STRUCT_DEFAULT_H
+#define SWIFT_BASIC_STRUCT_DEFAULT_H
+
+/**
+ * @brief Sink-related fields carried by each *gas* particle.
+ */
+struct sink_part_data {
+
+  /*! ID of the sink that will swallow this #part. */
+  long long swallow_id;
+};
+
+/**
+ * @brief Sink-related fields carried by each *sink* particle.
+ */
+struct sink_sink_data {
+
+  /*! ID of the sink that will swallow this #sink. */
+  long long swallow_id;
+
+  /*! Mass of the sink that will swallow this #sink. */
+  float swallow_mass;
+};
+
+/**
+ * @brief Return the ID of the sink that should swallow this #part.
+ *
+ * @param s_data The #part's #sink_part_data structure.
+ */
+__attribute__((always_inline)) INLINE static long long sink_get_part_swallow_id(
+    struct sink_part_data* s_data) {
+
+  return s_data->swallow_id;
+}
+
+/**
+ * @brief Update a given #part's sink data field to mark the particle has
+ * not yet been swallowed.
+ *
+ * @param s_data The #part's #sink_part_data structure.
+ */
+__attribute__((always_inline)) INLINE static void
+sink_mark_part_as_not_swallowed(struct sink_part_data* s_data) {
+
+  s_data->swallow_id = -1;
+}
+
+/**
+ * @brief Update a given #part's sink data field to mark the particle has
+ * having been been swallowed.
+ *
+ * @param p_data The #part's #sink_part_data structure.
+ */
+__attribute__((always_inline)) INLINE static void sink_mark_part_as_swallowed(
+    struct sink_part_data* s_data) {
+
+  s_data->swallow_id = -2;
+}
+
+/**
+ * @brief Update a given #sink's sink data field to mark the particle has
+ * not yet been swallowed.
+ *
+ * @param s_data The #sink's #sink_sink_data structure.
+ */
+__attribute__((always_inline)) INLINE static void
+sink_mark_sink_as_not_swallowed(struct sink_sink_data* s_data) {
+
+  s_data->swallow_id = -1;
+  s_data->swallow_mass = 0.f;
+}
+
+/**
+ * @brief Update a given #sink's sink data field to mark the particle has
+ * having been been swallowed.
+ *
+ * @param s_data The #sink's #bsink_sink_data structure.
+ */
+__attribute__((always_inline)) INLINE static void sink_mark_sink_as_merged(
+    struct sink_sink_data* s_data) {
+
+  s_data->swallow_id = -2;
+  s_data->swallow_mass = -1.f;
+}
+
+/**
+ * @brief Return the ID of the sink that should swallow this #sink.
+ *
+ * @param s_data The #sink's #sink_sink_data structure.
+ */
+__attribute__((always_inline)) INLINE static long long sink_get_sink_swallow_id(
+    struct sink_sink_data* s_data) {
+
+  return s_data->swallow_id;
+}
+
+#endif /* SWIFT_BASIC_STRUCT_DEFAULT_H */
diff --git a/src/sink/Default/sink.h b/src/sink/Default/sink.h
index e0d3f3b4a39f0d067d22c9bc088e481d5da9849a..1001987ae5054802f0b24eb5a9f166fe564c824a 100644
--- a/src/sink/Default/sink.h
+++ b/src/sink/Default/sink.h
@@ -81,7 +81,18 @@ __attribute__((always_inline)) INLINE static void sink_init_part(
 __attribute__((always_inline)) INLINE static void sink_init_sink(
     struct sink* sp) {
 
-  sp->num_ngbs = 0;
+  sp->density.wcount = 0.f;
+  sp->density.wcount_dh = 0.f;
+
+#ifdef SWIFT_SINK_DENSITY_CHECKS
+  sp->N_check_density = 0;
+  sp->N_check_density_exact = 0;
+  sp->rho_check = 0.f;
+  sp->rho_check_exact = 0.f;
+  sp->n_check = 0.f;
+  sp->n_check_exact = 0.f;
+  sp->inhibited_check_exact = 0;
+#endif
 }
 
 /**
@@ -118,7 +129,23 @@ __attribute__((always_inline)) INLINE static void sink_kick_extra(
  * @param cosmo The current cosmological model.
  */
 __attribute__((always_inline)) INLINE static void sink_end_density(
-    struct sink* si, const struct cosmology* cosmo) {}
+    struct sink* si, const struct cosmology* cosmo) {
+
+  /* Some smoothing length multiples. */
+  const float h = si->h;
+  const float h_inv = 1.0f / h;                       /* 1/h */
+  const float h_inv_dim = pow_dimension(h_inv);       /* 1/h^d */
+  const float h_inv_dim_plus_one = h_inv_dim * h_inv; /* 1/h^(d+1) */
+
+  /* Finish the calculation by inserting the missing h-factors */
+  si->density.wcount *= h_inv_dim;
+  si->density.wcount_dh *= h_inv_dim_plus_one;
+
+#ifdef SWIFT_SINK_DENSITY_CHECKS
+  si->rho_check *= h_inv_dim;
+  si->n_check *= h_inv_dim;
+#endif
+}
 
 /**
  * @brief Sets all particle fields to sensible values when the #sink has 0
@@ -128,7 +155,22 @@ __attribute__((always_inline)) INLINE static void sink_end_density(
  * @param cosmo The current cosmological model.
  */
 __attribute__((always_inline)) INLINE static void sinks_sink_has_no_neighbours(
-    struct sink* restrict sp, const struct cosmology* cosmo) {}
+    struct sink* restrict sp, const struct cosmology* cosmo) {
+
+  warning(
+      "Sink particle with ID %lld treated as having no neighbours (h: %g, "
+      "wcount: %g).",
+      sp->id, sp->h, sp->density.wcount);
+
+  /* Some smoothing length multiples. */
+  const float h = sp->h;
+  const float h_inv = 1.0f / h;                 /* 1/h */
+  const float h_inv_dim = pow_dimension(h_inv); /* 1/h^d */
+
+  /* Re-set problematic values */
+  sp->density.wcount = kernel_root * h_inv_dim;
+  sp->density.wcount_dh = 0.f;
+}
 
 /**
  * @brief Compute the accretion rate of the sink and any quantities
@@ -231,7 +273,11 @@ INLINE static void sink_copy_properties(
     const struct phys_const* phys_const,
     const struct hydro_props* restrict hydro_props,
     const struct unit_system* restrict us,
-    const struct cooling_function_data* restrict cooling) {}
+    const struct cooling_function_data* restrict cooling) {
+
+  /* Set a smoothing length */
+  sink->h = p->h;
+}
 
 /**
  * @brief Update the properties of a sink particles by swallowing
diff --git a/src/sink/Default/sink_iact.h b/src/sink/Default/sink_iact.h
index bae7b6129f15c8d248911caef72f36e901e2a790..653ae2fe058b5341eccda3994dddbe2bf6e43b4b 100644
--- a/src/sink/Default/sink_iact.h
+++ b/src/sink/Default/sink_iact.h
@@ -31,12 +31,11 @@
  * @param pj Second particle.
  * @param a Current scale factor.
  * @param H Current Hubble parameter.
- * @param cut_off_radius Sink cut off radius.
  */
 __attribute__((always_inline)) INLINE static void runner_iact_sink(
     const float r2, const float dx[3], const float hi, const float hj,
     struct part *restrict pi, struct part *restrict pj, const float a,
-    const float H, const float cut_off_radius) {}
+    const float H) {}
 
 /**
  * @brief do sink computation after the runner_iact_density (non symmetric
@@ -50,21 +49,18 @@ __attribute__((always_inline)) INLINE static void runner_iact_sink(
  * @param pj Second particle (not updated).
  * @param a Current scale factor.
  * @param H Current Hubble parameter.
- * @param cut_off_radius Sink cut off radius.
- * @param ti_current Current integer time value (for random numbers).
- * @param time current physical time in the simulation
  */
 __attribute__((always_inline)) INLINE static void runner_iact_nonsym_sink(
     const float r2, const float dx[3], const float hi, const float hj,
     struct part *restrict pi, const struct part *restrict pj, const float a,
-    const float H, const float cut_off_radius) {}
+    const float H) {}
 
 /**
  * @brief Density interaction between two particles (non-symmetric).
  *
  * @param r2 Comoving square distance between the two particles.
  * @param dx Comoving vector separating both particles (pi - pj).
- * @param ri Comoving cut off radius of particle i.
+ * @param hi Comoving smoothing length of particle i.
  * @param hj Comoving smoothing-length of particle j.
  * @param si First particle (sink).
  * @param pj Second particle (gas, not updated).
@@ -77,19 +73,31 @@ __attribute__((always_inline)) INLINE static void runner_iact_nonsym_sink(
  */
 __attribute__((always_inline)) INLINE static void
 runner_iact_nonsym_sinks_gas_density(
-    const float r2, const float dx[3], const float ri, const float hj,
+    const float r2, const float dx[3], const float hi, const float hj,
     struct sink *si, const struct part *pj, const int with_cosmology,
     const struct cosmology *cosmo, const struct gravity_props *grav_props,
     const struct sink_props *sink_props, const integertime_t ti_current,
     const double time) {
 
+  float wi, wi_dx;
+
   /* Get r. */
   const float r = sqrtf(r2);
 
-  if (r < ri) {
-    /* Contribution to the number of neighbours in cutoff radius */
-    si->num_ngbs++;
-  }
+  /* Compute the kernel function */
+  const float hi_inv = 1.0f / hi;
+  const float ui = r * hi_inv;
+  kernel_deval(ui, &wi, &wi_dx);
+
+  /* Compute contribution to the number of neighbours */
+  si->density.wcount += wi;
+  si->density.wcount_dh -= (hydro_dimension * wi + ui * wi_dx);
+
+#ifdef SWIFT_SINK_DENSITY_CHECKS
+  si->rho_check += hydro_get_mass(pj) * wi;
+  si->n_check += wi;
+  si->N_check_density++;
+#endif
 }
 
 /**
@@ -97,8 +105,8 @@ runner_iact_nonsym_sinks_gas_density(
  *
  * @param r2 Comoving square distance between the two particles.
  * @param dx Comoving vector separating both particles (pi - pj).
- * @param ri Comoving cut off radius of particle i.
- * @param rj Comoving cut off radius of particle j.
+ * @param hi Comoving smoothing length of particle i.
+ * @param hj Comoving smoothing length of particle j.
  * @param si First sink particle.
  * @param sj Second sink particle.
  * @param with_cosmology if we run with cosmology.
@@ -110,7 +118,7 @@ runner_iact_nonsym_sinks_gas_density(
  */
 __attribute__((always_inline)) INLINE static void
 runner_iact_nonsym_sinks_sink_swallow(
-    const float r2, const float dx[3], const float ri, const float rj,
+    const float r2, const float dx[3], const float hi, const float hj,
     struct sink *restrict si, struct sink *restrict sj,
     const int with_cosmology, const struct cosmology *cosmo,
     const struct gravity_props *grav_props,
@@ -122,7 +130,7 @@ runner_iact_nonsym_sinks_sink_swallow(
  *
  * @param r2 Comoving square distance between the two particles.
  * @param dx Comoving vector separating both particles (pi - pj).
- * @param ri Comoving cut off radius of particle i.
+ * @param hi Comoving smoothing length of particle i.
  * @param hj Comoving smoothing-length of particle j.
  * @param si First sink particle.
  * @param pj Second particle.
@@ -135,7 +143,7 @@ runner_iact_nonsym_sinks_sink_swallow(
  */
 __attribute__((always_inline)) INLINE static void
 runner_iact_nonsym_sinks_gas_swallow(
-    const float r2, const float dx[3], const float ri, const float hj,
+    const float r2, const float dx[3], const float hi, const float hj,
     struct sink *restrict si, struct part *restrict pj,
     const int with_cosmology, const struct cosmology *cosmo,
     const struct gravity_props *grav_props,
diff --git a/src/sink/Default/sink_io.h b/src/sink/Default/sink_io.h
index 734dbb3309b3948352d600775f6c98489657d84b..c4fd30737697a61be167453e8eaf584cf4ab6349 100644
--- a/src/sink/Default/sink_io.h
+++ b/src/sink/Default/sink_io.h
@@ -33,7 +33,7 @@ INLINE static void sink_read_particles(struct sink* sinks,
                                        struct io_props* list, int* num_fields) {
 
   /* Say how much we want to read */
-  *num_fields = 4;
+  *num_fields = 5;
 
   /* List what we want to read */
   list[0] = io_make_input_field("Coordinates", DOUBLE, 3, COMPULSORY,
@@ -44,6 +44,8 @@ INLINE static void sink_read_particles(struct sink* sinks,
                                 sinks, mass);
   list[3] = io_make_input_field("ParticleIDs", LONGLONG, 1, COMPULSORY,
                                 UNIT_CONV_NO_UNITS, sinks, id);
+  list[4] = io_make_input_field("SmoothingLength", FLOAT, 1, OPTIONAL,
+                                UNIT_CONV_LENGTH, sinks, h);
 }
 
 INLINE static void convert_sink_pos(const struct engine* e,
@@ -107,7 +109,7 @@ INLINE static void sink_write_particles(const struct sink* sinks,
                                         int with_cosmology) {
 
   /* Say how much we want to write */
-  *num_fields = 4;
+  *num_fields = 5;
 
   /* List what we want to write */
   list[0] = io_make_output_field_convert_sink(
@@ -126,6 +128,10 @@ INLINE static void sink_write_particles(const struct sink* sinks,
       "ParticleIDs", ULONGLONG, 1, UNIT_CONV_NO_UNITS, 0.f, sinks, id,
       /*can convert to comoving=*/0, "Unique ID of the particles");
 
+  list[4] = io_make_output_field(
+      "SmoothingLengths", FLOAT, 1, UNIT_CONV_LENGTH, 1.f, sinks, h,
+      "Co-moving smoothing lengths (FWHM of the kernel) of the particles");
+
 #ifdef DEBUG_INTERACTIONS_SINKS
 
   list += *num_fields;
diff --git a/src/sink/Default/sink_part.h b/src/sink/Default/sink_part.h
index 284bcc276a74061920c7bbcb1ae2bf15d6fa22f9..33dbf66dad11249852aa0196386b0172ef3d37bb 100644
--- a/src/sink/Default/sink_part.h
+++ b/src/sink/Default/sink_part.h
@@ -57,8 +57,18 @@ struct sink {
   /*! Particle velocity. */
   float v[3];
 
-  /*! Cut off radius. */
-  float r_cut;
+  /* Particle smoothing length */
+  float h;
+
+  struct {
+
+    /* Number of neighbours. */
+    float wcount;
+
+    /* Number of neighbours spatial derivative. */
+    float wcount_dh;
+
+  } density;
 
   /*! Sink particle mass */
   float mass;
@@ -72,6 +82,9 @@ struct sink {
   /*! Particle time bin */
   timebin_t time_bin;
 
+  /*! Tree-depth at which size / 2 <= h * gamma < size */
+  char depth_h;
+
   /*! Integer number of neighbours */
   int num_ngbs;
 
@@ -111,6 +124,29 @@ struct sink {
   /*! List of interacting particles in compute formation SELF and PAIR */
   long long ids_ngbs_accretion[MAX_NUM_OF_NEIGHBOURS_SINKS];
 #endif
+
+#ifdef SWIFT_SINK_DENSITY_CHECKS
+
+  /* Integer number of neighbours in the density loop */
+  int N_check_density;
+
+  /* Exact integer number of neighbours in the density loop */
+  int N_check_density_exact;
+
+  /*! Has this particle interacted with any unhibited neighbour? */
+  char inhibited_check_exact;
+
+  float n_check;
+
+  float n_check_exact;
+
+  float rho_check;
+
+  /*! Exact value of the density field obtained via brute-force loop */
+  float rho_check_exact;
+
+#endif
+
 } SWIFT_STRUCT_ALIGN;
 
 #endif /* SWIFT_DEFAULT_SINK_PART_H */
diff --git a/src/sink/Default/sink_properties.h b/src/sink/Default/sink_properties.h
index 08b458c77597daba7d94903d71af895ad925c55e..f9bd54e74996ab3371fec36b82aa31130766a842 100644
--- a/src/sink/Default/sink_properties.h
+++ b/src/sink/Default/sink_properties.h
@@ -19,13 +19,38 @@
 #ifndef SWIFT_DEFAULT_SINK_PROPERTIES_H
 #define SWIFT_DEFAULT_SINK_PROPERTIES_H
 
+/* Local header */
+#include "feedback_properties.h"
+#include "parser.h"
+
 /**
  * @brief Properties of sink in the Default model.
  */
 struct sink_props {
 
-  /*! Cut off radius */
-  float cut_off_radius;
+  /* ----- Basic neighbour search properties ------ */
+
+  /*! Resolution parameter */
+  float eta_neighbours;
+
+  /*! Target weightd number of neighbours (for info only)*/
+  float target_neighbours;
+
+  /*! Smoothing length tolerance */
+  float h_tolerance;
+
+  /*! Tolerance on neighbour number  (for info only)*/
+  float delta_neighbours;
+
+  /*! Maximal number of iterations to converge h */
+  int max_smoothing_iterations;
+
+  /*! Maximal change of h over one time-step */
+  float log_max_h_change;
+
+  /*! Are we using a fixed cutoff radius? (all smoothing length calculations are
+   * disabled if so) */
+  char use_fixed_r_cut;
 };
 
 /**
@@ -38,16 +63,48 @@ struct sink_props {
  * @param cosmo The cosmological model.
  * @param with_feedback Are we running with feedback?
  */
-INLINE static void sink_props_init(struct sink_props *sp,
-                                   struct feedback_props *fp,
-                                   const struct phys_const *phys_const,
-                                   const struct unit_system *us,
-                                   struct swift_params *params,
-                                   const struct cosmology *cosmo,
-                                   const int with_feedback) {
-
-  sp->cut_off_radius =
-      parser_get_param_float(params, "DefaultSink:cut_off_radius");
+INLINE static void sink_props_init(
+    struct sink_props *sp, struct feedback_props *fp,
+    const struct phys_const *phys_const, const struct unit_system *us,
+    struct swift_params *params, const struct hydro_props *hydro_props,
+    const struct cosmology *cosmo, const int with_feedback) {
+
+  /* Read in the basic neighbour search properties or default to the hydro
+     ones if the user did not provide any different values */
+
+  /* Kernel properties */
+  sp->eta_neighbours = parser_get_opt_param_float(
+      params, "Sinks:resolution_eta", hydro_props->eta_neighbours);
+
+  /* Tolerance for the smoothing length Newton-Raphson scheme */
+  sp->h_tolerance = parser_get_opt_param_float(params, "Sinks:h_tolerance",
+                                               hydro_props->h_tolerance);
+
+  /* Get derived properties */
+  sp->target_neighbours = pow_dimension(sp->eta_neighbours) * kernel_norm;
+  const float delta_eta = sp->eta_neighbours * (1.f + sp->h_tolerance);
+  sp->delta_neighbours =
+      (pow_dimension(delta_eta) - pow_dimension(sp->eta_neighbours)) *
+      kernel_norm;
+
+  /* Number of iterations to converge h */
+  sp->max_smoothing_iterations =
+      parser_get_opt_param_int(params, "Sinks:max_ghost_iterations",
+                               hydro_props->max_smoothing_iterations);
+
+  /* Time integration properties */
+  const float max_volume_change =
+      parser_get_opt_param_float(params, "Sinks:max_volume_change", -1);
+  if (max_volume_change == -1)
+    sp->log_max_h_change = hydro_props->log_max_h_change;
+  else
+    sp->log_max_h_change = logf(powf(max_volume_change, hydro_dimension_inv));
+
+  /* This property determines whether we're using a fixed cutoff radius (and
+   * smoothing length) for the sink. It must always be present, as we use it
+   * to skip smoothing length iterations in runner_ghost if a fixed cutoff is
+   * being used. */
+  sp->use_fixed_r_cut = 0;
 }
 
 /**
diff --git a/src/sink/GEAR/sink.h b/src/sink/GEAR/sink.h
index 627713379e85049f13ee709242efdd6e168f9e53..f2b5c000995e74b7d4a7f404e0170a710de482ea 100644
--- a/src/sink/GEAR/sink.h
+++ b/src/sink/GEAR/sink.h
@@ -77,9 +77,7 @@ __attribute__((always_inline)) INLINE static float sink_compute_timestep(
       sink->to_collect.sound_speed_gas * cosmo->a_factor_sound_speed;
   const double gas_c_phys2 = gas_c_phys * gas_c_phys;
   const float denominator = sqrtf(gas_c_phys2 + gas_v_norm2);
-  const float h_min =
-      cosmo->a *
-      min(sink->r_cut, sink->to_collect.minimal_h_gas * kernel_gamma);
+  const float h_min = cosmo->a * min(sink->h, sink->to_collect.minimal_h_gas);
   float dt_cfl = 0.0;
 
   /* This case can happen if the sink is just born. */
@@ -167,7 +165,6 @@ __attribute__((always_inline)) INLINE static void sink_first_init_sink(
     struct sink* sp, const struct sink_props* sink_props,
     const struct engine* e) {
 
-  sp->r_cut = sink_props->cut_off_radius;
   sp->time_bin = 0;
 
   sp->number_of_gas_swallows = 0;
@@ -255,6 +252,9 @@ __attribute__((always_inline)) INLINE static void sink_init_part(
 __attribute__((always_inline)) INLINE static void sink_init_sink(
     struct sink* sp) {
 
+  sp->density.wcount = 0.f;
+  sp->density.wcount_dh = 0.f;
+
   /* Reset to the mass of the sink */
   sp->mass_tot_before_star_spawning = sp->mass;
 
@@ -284,6 +284,16 @@ __attribute__((always_inline)) INLINE static void sink_init_sink(
     sp->ids_ngbs_formation[i] = -1;
   sp->num_ngb_formation = 0;
 #endif
+
+#ifdef SWIFT_SINK_DENSITY_CHECKS
+  sp->N_check_density = 0;
+  sp->N_check_density_exact = 0;
+  sp->rho_check = 0.f;
+  sp->rho_check_exact = 0.f;
+  sp->n_check = 0.f;
+  sp->n_check_exact = 0.f;
+  sp->inhibited_check_exact = 0;
+#endif
 }
 
 /**
@@ -322,9 +332,10 @@ __attribute__((always_inline)) INLINE static void sink_kick_extra(
 __attribute__((always_inline)) INLINE static void sink_end_density(
     struct sink* si, const struct cosmology* cosmo) {
 
-  const float h = si->r_cut / kernel_gamma;
-  const float h_inv = 1.0f / h;                 /* 1/h */
-  const float h_inv_dim = pow_dimension(h_inv); /* 1/h^d */
+  const float h = si->h;
+  const float h_inv = 1.0f / h;                       /* 1/h */
+  const float h_inv_dim = pow_dimension(h_inv);       /* 1/h^d */
+  const float h_inv_dim_plus_one = h_inv_dim * h_inv; /* 1/h^(d+1) */
 
   /* --- Finish the calculation by inserting the missing h factors --- */
   si->to_collect.rho_gas *= h_inv_dim;
@@ -336,6 +347,15 @@ __attribute__((always_inline)) INLINE static void sink_end_density(
   si->to_collect.velocity_gas[0] *= h_inv_dim * rho_inv;
   si->to_collect.velocity_gas[1] *= h_inv_dim * rho_inv;
   si->to_collect.velocity_gas[2] *= h_inv_dim * rho_inv;
+
+  /* Finish the calculation by inserting the missing h-factors */
+  si->density.wcount *= h_inv_dim;
+  si->density.wcount_dh *= h_inv_dim_plus_one;
+
+#ifdef SWIFT_SINK_DENSITY_CHECKS
+  si->rho_check *= h_inv_dim;
+  si->n_check *= h_inv_dim;
+#endif
 }
 
 /**
@@ -349,15 +369,22 @@ __attribute__((always_inline)) INLINE static void sinks_sink_has_no_neighbours(
     struct sink* restrict sp, const struct cosmology* cosmo) {
 
   warning(
-      "Sink particle with ID %lld treated as having no neighbours (r_cut: %g, "
+      "Sink particle with ID %lld treated as having no neighbours (h: %g, "
       "numb_ngbs: %i).",
-      sp->id, sp->r_cut, sp->num_ngbs);
+      sp->id, sp->h, sp->num_ngbs);
+
+  /* Some smoothing length multiples. */
+  const float h = sp->h;
+  const float h_inv = 1.0f / h;                 /* 1/h */
+  const float h_inv_dim = pow_dimension(h_inv); /* 1/h^d */
 
   /* Reset problematic values */
   sp->to_collect.velocity_gas[0] = sp->v[0];
   sp->to_collect.velocity_gas[1] = sp->v[1];
   sp->to_collect.velocity_gas[2] = sp->v[2];
-  sp->to_collect.minimal_h_gas = sp->r_cut / kernel_gamma;
+  sp->to_collect.minimal_h_gas = h;
+  sp->density.wcount = kernel_root * h_inv_dim;
+  sp->density.wcount_dh = 0.f;
 }
 
 /**
@@ -548,7 +575,11 @@ INLINE static void sink_copy_properties(
   sink_init_sink(sink);
 
   /* Set a smoothing length */
-  sink->r_cut = sink_props->cut_off_radius;
+  if (sink_props->use_fixed_r_cut) {
+    sink->h = sink_props->cut_off_radius / kernel_gamma;
+  } else {
+    sink->h = p->h;
+  }
 
   /* Flag it as not swallowed */
   sink_mark_sink_as_not_swallowed(&sink->merger_data);
@@ -776,7 +807,7 @@ INLINE static int sink_spawn_star(struct sink* sink, const struct engine* e,
  * @brief Give the #spart a new position.
  *
  * In GEAR: Positions are set by randomly sampling coordinates in an homogeneous
- * sphere centered on the #sink with radius  the sink's r_cut.
+ * sphere centered on the #sink with radius the sink's r_cut.
  *
  * @param e The #engine.
  * @param si The #sink generating a star.
@@ -798,8 +829,9 @@ INLINE static void sink_star_formation_give_new_position(const struct engine* e,
   const double phi =
       2 * M_PI *
       random_unit_interval(sp->id, e->ti_current, (enum random_number_type)3);
-  const double r = si->r_cut * random_unit_interval(sp->id, e->ti_current,
-                                                    (enum random_number_type)4);
+  const float rmax = si->h * kernel_gamma;
+  const double r = rmax * random_unit_interval(sp->id, e->ti_current,
+                                               (enum random_number_type)4);
   const double cos_theta =
       1.0 - 2.0 * random_unit_interval(sp->id, e->ti_current,
                                        (enum random_number_type)5);
@@ -837,8 +869,8 @@ INLINE static void sink_star_formation_give_new_velocity(
      and subtracted from the sink. */
   double v_given[3] = {0.0, 0.0, 0.0};
   const double G_newton = e->physical_constants->const_newton_G;
-  const double sigma_2 =
-      G_newton * si->mass_tot_before_star_spawning / si->r_cut;
+  const float rmax = si->h * kernel_gamma;
+  const double sigma_2 = G_newton * si->mass_tot_before_star_spawning / rmax;
   const double sigma = sink_props->star_spawning_sigma_factor * sqrt(sigma_2);
 
   for (int i = 0; i < 3; ++i) {
@@ -900,7 +932,7 @@ INLINE static void sink_copy_properties_to_star(
   sink_star_formation_give_new_velocity(e, sink, sp, sink_props);
 
   /* Sph smoothing length */
-  sp->h = sink->r_cut;
+  sp->h = sink->h;
 
   /* Feedback related initialisation */
   /* ------------------------------- */
@@ -1205,7 +1237,8 @@ INLINE static void sink_prepare_part_sink_formation_sink_criteria(
   const float r_acc_p = sink_props->cut_off_radius * cosmo->a;
 
   /* Physical accretion radius of sink si */
-  const float r_acc_si = si->r_cut * cosmo->a;
+  const float rmax = si->h * kernel_gamma;
+  const float r_acc_si = rmax * cosmo->a;
 
   /* Comoving distance of particl p */
   const float px[3] = {(float)(p->x[0]), (float)(p->x[1]), (float)(p->x[2])};
diff --git a/src/sink/GEAR/sink_iact.h b/src/sink/GEAR/sink_iact.h
index 15de6b14ab82dccae1731a6c301009dc47d5e48e..d52d6b94f917df2bcab6440b8b3e80856ff9b385 100644
--- a/src/sink/GEAR/sink_iact.h
+++ b/src/sink/GEAR/sink_iact.h
@@ -42,21 +42,21 @@
  * @param pj Second particle.
  * @param a Current scale factor.
  * @param H Current Hubble parameter.
- * @param cut_off_radius Sink cut off radius.
  */
 __attribute__((always_inline)) INLINE static void runner_iact_sink(
     const float r2, const float dx[3], const float hi, const float hj,
     struct part *restrict pi, struct part *restrict pj, const float a,
-    const float H, const float cut_off_radius) {
+    const float H) {
 
-  /* In order to prevent the formation of two sink particles at a distance
-   * smaller than the sink cutoff radius, we keep only gas particles with
-   * the smallest potential. */
+  /* In order to prevent the formation of two sink particles too close together,
+   * we keep only gas particles with the smallest potential. The distance at
+   * which to prevent sink formation is the cutoff radius if this is fixed, or
+   * it is the variable smoothing length times gamma. */
 
   const float r = sqrtf(r2);
+  const float rmax = max(hi, hj) * kernel_gamma;
 
-  /* if the distance is less than the cut off radius */
-  if (r < cut_off_radius) {
+  if (r < rmax) {
     float potential_i = pi->sink_data.potential;
     float potential_j = pj->sink_data.potential;
 
@@ -88,20 +88,21 @@ __attribute__((always_inline)) INLINE static void runner_iact_sink(
  * @param pj Second particle (not updated).
  * @param a Current scale factor.
  * @param H Current Hubble parameter.
- * @param cut_off_radius Sink cut off radius.
  */
 __attribute__((always_inline)) INLINE static void runner_iact_nonsym_sink(
     const float r2, const float dx[3], const float hi, const float hj,
     struct part *restrict pi, const struct part *restrict pj, const float a,
-    const float H, const float cut_off_radius) {
+    const float H) {
 
-  /* In order to prevent the formation of two sink particles at a distance
-   * smaller than the sink cutoff radius, we keep only gas particles with
-   * the smallest potential. */
+  /* In order to prevent the formation of two sink particles too close together,
+   * we keep only gas particles with the smallest potential. The distance at
+   * which to prevent sink formation is the cutoff radius if this is fixed, or
+   * it is the variable smoothing length times gamma. */
 
   const float r = sqrtf(r2);
+  const float rmax = max(hi, hj) * kernel_gamma;
 
-  if (r < cut_off_radius) {
+  if (r < rmax) {
     float potential_i = pi->sink_data.potential;
     float potential_j = pj->sink_data.potential;
 
@@ -111,7 +112,12 @@ __attribute__((always_inline)) INLINE static void runner_iact_nonsym_sink(
   }
 }
 
-/* @brief Density interaction between two particles (non-symmetric).
+/**
+ * @brief Density interaction between two particles (non-symmetric).
+ *
+ * @param r2 Comoving square distance between the two particles.
+ * @param dx Comoving vector separating both particles (pi - pj).
+ * @param hi Comoving smoothing length of particle i.
  * @param hj Comoving smoothing-length of particle j.
  * @param si First particle (sink).
  * @param pj Second particle (gas, not updated).
@@ -124,7 +130,7 @@ __attribute__((always_inline)) INLINE static void runner_iact_nonsym_sink(
  */
 __attribute__((always_inline)) INLINE static void
 runner_iact_nonsym_sinks_gas_density(
-    const float r2, const float dx[3], const float ri, const float hj,
+    const float r2, const float dx[3], const float hi, const float hj,
     struct sink *si, const struct part *pj, const int with_cosmology,
     const struct cosmology *cosmo, const struct gravity_props *grav_props,
     const struct sink_props *sink_props, const integertime_t ti_current,
@@ -137,7 +143,6 @@ runner_iact_nonsym_sinks_gas_density(
 
   /* Compute the kernel function */
   const float r = sqrtf(r2);
-  const float hi = ri / kernel_gamma;
   const float hi_inv = 1.0f / hi;
   const float ui = r * hi_inv;
   kernel_deval(ui, &wi, &wi_dx);
@@ -165,6 +170,16 @@ runner_iact_nonsym_sinks_gas_density(
   si->to_collect.velocity_gas[0] += mj * dv[0] * wi;
   si->to_collect.velocity_gas[1] += mj * dv[1] * wi;
   si->to_collect.velocity_gas[2] += mj * dv[2] * wi;
+
+  /* Compute contribution to the number of neighbours */
+  si->density.wcount += wi;
+  si->density.wcount_dh -= (hydro_dimension * wi + ui * wi_dx);
+
+#ifdef SWIFT_SINK_DENSITY_CHECKS
+  si->rho_check += hydro_get_mass(pj) * wi;
+  si->n_check += wi;
+  si->N_check_density++;
+#endif
 }
 
 /**
@@ -175,18 +190,14 @@ runner_iact_nonsym_sinks_gas_density(
  *
  * @param r2 Comoving square distance between the two particles.
  * @param dx Comoving vector separating both particles (pi - pj).
- * @param ri Comoving cut off radius of particle i.
- * @param rj Comoving cut off radius of particle j.
+ * @param hi Comoving smoothing length of particle i.
+ * @param hj Comoving smoothing length of particle j.
  * @param si First sink particle.
  * @param sj Second sink particle.
- * @param with_cosmology if we run with cosmology.
- * @param cosmo The cosmological parameters and properties.
- * @param grav_props The gravity scheme parameters and properties.
- * @param sink_props the sink properties to use.
  */
 __attribute__((always_inline)) INLINE static void
 sink_collect_properties_from_sink(const float r2, const float dx[3],
-                                  const float ri, const float rj,
+                                  const float hi, const float hj,
                                   struct sink *restrict si,
                                   struct sink *restrict sj,
                                   const struct gravity_props *grav_props) {
@@ -233,8 +244,8 @@ sink_collect_properties_from_sink(const float r2, const float dx[3],
  *
  * @param r2 Comoving square distance between the two particles.
  * @param dx Comoving vector separating both particles (pi - pj).
- * @param ri Comoving cut off radius of particle i.
- * @param rj Comoving cut off radius of particle j.
+ * @param hi Comoving smoothing length of particle i.
+ * @param hj Comoving smoothing length of particle j.
  * @param si First sink particle.
  * @param sj Second sink particle.
  * @param with_cosmology if we run with cosmology.
@@ -246,15 +257,18 @@ sink_collect_properties_from_sink(const float r2, const float dx[3],
  */
 __attribute__((always_inline)) INLINE static void
 runner_iact_nonsym_sinks_sink_swallow(
-    const float r2, const float dx[3], const float ri, const float rj,
+    const float r2, const float dx[3], const float hi, const float hj,
     struct sink *restrict si, struct sink *restrict sj,
     const int with_cosmology, const struct cosmology *cosmo,
     const struct gravity_props *grav_props,
     const struct sink_props *sink_properties, const integertime_t ti_current,
     const double time) {
 
+  /* Convert the smoothing length back into a cutoff radius */
+  const float hig = hi * kernel_gamma;
+
   const float r = sqrtf(r2);
-  const float f_acc_r_acc_i = sink_properties->f_acc * ri;
+  const float f_acc_r_acc_i = sink_properties->f_acc * hig;
 
   /* Determine if the sink is dead, i.e. if its age is bigger than the
      age_threshold_unlimited */
@@ -268,7 +282,7 @@ runner_iact_nonsym_sinks_sink_swallow(
      they are both dead, we do not want to restrict the timesteps for 2-body
      encounters since they won't merge. */
   if (!si_is_dead || !sj_is_dead) {
-    sink_collect_properties_from_sink(r2, dx, ri, rj, si, sj, grav_props);
+    sink_collect_properties_from_sink(r2, dx, hi, hj, si, sj, grav_props);
   }
 
   /* If si is dead, do not swallow sj. However, sj can swallow si if it alive.
@@ -322,9 +336,9 @@ runner_iact_nonsym_sinks_sink_swallow(
     /* Momentum check------------------------------------------------------- */
     float L2_j = 0.0;      /* Relative momentum of the sink j */
     float L2_kepler = 0.0; /* Keplerian angular momentum squared */
-    sink_compute_angular_momenta_criterion(dx, v_plus_H_flow, r, si->r_cut,
-                                           si->mass, cosmo, grav_props,
-                                           &L2_kepler, &L2_j);
+    sink_compute_angular_momenta_criterion(
+        dx, v_plus_H_flow, r, si->h * kernel_gamma, si->mass, cosmo, grav_props,
+        &L2_kepler, &L2_j);
 
     /* To be accreted, the sink momentum should lower than the keplerian orbit
      * momentum. */
@@ -420,7 +434,7 @@ runner_iact_nonsym_sinks_sink_swallow(
  *
  * @param r2 Comoving square distance between the two particles.
  * @param dx Comoving vector separating both particles (pi - pj).
- * @param ri Comoving cut off radius of particle i.
+ * @param hi Comoving smoothing length of particle i.
  * @param hj Comoving smoothing-length of particle j.
  * @param si First sink particle.
  * @param pj Second particle.
@@ -433,15 +447,18 @@ runner_iact_nonsym_sinks_sink_swallow(
  */
 __attribute__((always_inline)) INLINE static void
 runner_iact_nonsym_sinks_gas_swallow(
-    const float r2, const float dx[3], const float ri, const float hj,
+    const float r2, const float dx[3], const float hi, const float hj,
     struct sink *restrict si, struct part *restrict pj,
     const int with_cosmology, const struct cosmology *cosmo,
     const struct gravity_props *grav_props,
     const struct sink_props *sink_properties, const integertime_t ti_current,
     const double time) {
 
+  /* Convert the smoothing length back into a cutoff radius */
+  const float hig = hi * kernel_gamma;
+
   const float r = sqrtf(r2);
-  const float f_acc_r_acc = sink_properties->f_acc * ri;
+  const float f_acc_r_acc = sink_properties->f_acc * hig;
 
   /* Determine if the sink is dead, i.e. if its age is bigger than the
      age_threshold_unlimited */
@@ -464,7 +481,7 @@ runner_iact_nonsym_sinks_gas_swallow(
     }
 
     /* f_acc*r_acc <= r <= r_acc, we perform other checks */
-  } else if ((r >= f_acc_r_acc) && (r < ri)) {
+  } else if ((r >= f_acc_r_acc) && (r < hig)) {
 
     /* Relative velocity between the sinks */
     const float dv[3] = {pj->v[0] - si->v[0], pj->v[1] - si->v[1],
@@ -490,9 +507,9 @@ runner_iact_nonsym_sinks_gas_swallow(
     /* Momentum check------------------------------------------------------- */
     float L2_gas_j = 0.0;  /* Relative momentum of the gas */
     float L2_kepler = 0.0; /* Keplerian angular momentum squared */
-    sink_compute_angular_momenta_criterion(dx, v_plus_H_flow, r, si->r_cut,
-                                           si->mass, cosmo, grav_props,
-                                           &L2_kepler, &L2_gas_j);
+    sink_compute_angular_momenta_criterion(
+        dx, v_plus_H_flow, r, si->h * kernel_gamma, si->mass, cosmo, grav_props,
+        &L2_kepler, &L2_gas_j);
 
     /* To be accreted, the gas momentum should lower than the keplerian orbit
      * momentum. */
@@ -532,10 +549,10 @@ runner_iact_nonsym_sinks_gas_swallow(
     if (E_mec_sink_part >= 0) return;
 
     /* To be accreted, the gas smoothing length must be smaller than the sink
-       accretion radius. This is similar to AMR codes requesting the maximum
+       smoothing length. This is similar to AMR codes requesting the maximum
        refinement level close to the sink. */
     if (sink_properties->sink_formation_smoothing_length_criterion &&
-        (pj->h * kernel_gamma >= si->r_cut))
+        (pj->h >= si->h))
       return;
 
     /* Most bound pair check------------------------------------------------ */
diff --git a/src/sink/GEAR/sink_io.h b/src/sink/GEAR/sink_io.h
index 293d58d50175657c155701c07b28ddd9da61ad4d..fe86573da2656253910489946350e2aa42b3e3da 100644
--- a/src/sink/GEAR/sink_io.h
+++ b/src/sink/GEAR/sink_io.h
@@ -34,7 +34,7 @@ INLINE static void sink_read_particles(struct sink* sinks,
                                        struct io_props* list, int* num_fields) {
 
   /* Say how much we want to read */
-  *num_fields = 5;
+  *num_fields = 6;
 
   /* List what we want to read */
   list[0] = io_make_input_field("Coordinates", DOUBLE, 3, COMPULSORY,
@@ -45,7 +45,9 @@ INLINE static void sink_read_particles(struct sink* sinks,
                                 sinks, mass);
   list[3] = io_make_input_field("ParticleIDs", LONGLONG, 1, COMPULSORY,
                                 UNIT_CONV_NO_UNITS, sinks, id);
-  list[4] = io_make_input_field("BirthTime", FLOAT, 1, OPTIONAL, UNIT_CONV_MASS,
+  list[4] = io_make_input_field("SmoothingLength", FLOAT, 1, OPTIONAL,
+                                UNIT_CONV_LENGTH, sinks, h);
+  list[5] = io_make_input_field("BirthTime", FLOAT, 1, OPTIONAL, UNIT_CONV_MASS,
                                 sinks, birth_time);
 }
 
@@ -144,21 +146,25 @@ INLINE static void sink_write_particles(const struct sink* sinks,
       "ParticleIDs", ULONGLONG, 1, UNIT_CONV_NO_UNITS, 0.f, sinks, id,
       /*can convert to comoving=*/0, "Unique ID of the particles");
 
-  list[4] = io_make_physical_output_field(
+  list[4] = io_make_output_field(
+      "SmoothingLengths", FLOAT, 1, UNIT_CONV_LENGTH, 1.f, sinks, h,
+      "Co-moving smoothing lengths (FWHM of the kernel) of the particles");
+
+  list[5] = io_make_physical_output_field(
       "NumberOfSinkSwallows", INT, 1, UNIT_CONV_NO_UNITS, 0.f, sinks,
       number_of_sink_swallows, /*can convert to comoving=*/0,
       "Total number of sink merger events");
 
-  list[5] = io_make_physical_output_field(
+  list[6] = io_make_physical_output_field(
       "NumberOfGasSwallows", INT, 1, UNIT_CONV_NO_UNITS, 0.f, sinks,
       number_of_gas_swallows, /*can convert to comoving=*/0,
       "Total number of gas merger events");
 
-  list[6] = io_make_output_field_convert_sink(
+  list[7] = io_make_output_field_convert_sink(
       "TargetMass", FLOAT, 1, UNIT_CONV_MASS, 0.f, sinks,
       convert_sink_target_mass, "Sink target mass to spawn star particles");
 
-  list[7] = io_make_physical_output_field(
+  list[8] = io_make_physical_output_field(
       "Nstars", INT, 1, UNIT_CONV_NO_UNITS, 0.f, sinks, n_stars,
       /*can convert to comoving=*/0,
       "Number of stars spawned by the sink particles");
@@ -166,7 +172,7 @@ INLINE static void sink_write_particles(const struct sink* sinks,
   /* Note: Since the swallowed momentum is computed with the physical velocity,
      i.e. including the Hubble flow term, it is not convertible to comoving
      frame. */
-  list[8] = io_make_physical_output_field_convert_sink(
+  list[9] = io_make_physical_output_field_convert_sink(
       "SwallowedAngularMomentum", FLOAT, 3, UNIT_CONV_ANGULAR_MOMENTUM, 0.f,
       sinks,
       /*can convert to comoving=*/0, convert_sink_swallowed_angular_momentum,
diff --git a/src/sink/GEAR/sink_part.h b/src/sink/GEAR/sink_part.h
index 80dded0ef11eec301d93af53bf309f8b2a63e8b1..1f5390397745ae77c98c28383c24538a28f445cf 100644
--- a/src/sink/GEAR/sink_part.h
+++ b/src/sink/GEAR/sink_part.h
@@ -46,8 +46,18 @@ struct sink {
   /*! Particle velocity. */
   float v[3];
 
-  /*! Cut off radius. */
-  float r_cut;
+  /* Particle smoothing length, or r_cut/kernel_gamma if using a fixed cutoff*/
+  float h;
+
+  struct {
+
+    /* Number of neighbours. */
+    float wcount;
+
+    /* Number of neighbours spatial derivative. */
+    float wcount_dh;
+
+  } density;
 
   /*! Sink particle mass */
   float mass;
@@ -108,6 +118,9 @@ struct sink {
   /*! Particle time bin */
   timebin_t time_bin;
 
+  /*! Tree-depth at which size / 2 <= h * gamma < size */
+  char depth_h;
+
   /*! Number of stars spawned by this sink */
   int n_stars;
 
@@ -170,6 +183,28 @@ struct sink {
   /*! List of interacting particles in compute formation SELF and PAIR */
   long long ids_ngbs_accretion[MAX_NUM_OF_NEIGHBOURS_SINKS];
 #endif
+
+#ifdef SWIFT_SINK_DENSITY_CHECKS
+
+  /* Integer number of neighbours in the density loop */
+  int N_check_density;
+
+  /* Exact integer number of neighbours in the density loop */
+  int N_check_density_exact;
+
+  /*! Has this particle interacted with any unhibited neighbour? */
+  char inhibited_check_exact;
+
+  float n_check;
+
+  float n_check_exact;
+
+  float rho_check;
+
+  /*! Exact value of the density field obtained via brute-force loop */
+  float rho_check_exact;
+
+#endif
 } SWIFT_STRUCT_ALIGN;
 
 #endif /* SWIFT_GEAR_SINK_PART_H */
diff --git a/src/sink/GEAR/sink_properties.h b/src/sink/GEAR/sink_properties.h
index 5b60b61d369fbbc3c1b0554278b766e3a93cd4a9..53e3f3455a5aa5e20b1f1ba30964ca71f0ace57a 100644
--- a/src/sink/GEAR/sink_properties.h
+++ b/src/sink/GEAR/sink_properties.h
@@ -41,6 +41,30 @@
  */
 struct sink_props {
 
+  /* ----- Basic neighbour search properties ------ */
+
+  /*! Resolution parameter */
+  float eta_neighbours;
+
+  /*! Target weightd number of neighbours (for info only)*/
+  float target_neighbours;
+
+  /*! Smoothing length tolerance */
+  float h_tolerance;
+
+  /*! Tolerance on neighbour number  (for info only)*/
+  float delta_neighbours;
+
+  /*! Maximal number of iterations to converge h */
+  int max_smoothing_iterations;
+
+  /*! Maximal change of h over one time-step */
+  float log_max_h_change;
+
+  /*! Are we using a fixed cutoff radius? (all smoothing length calculations are
+   * disabled if so) */
+  char use_fixed_r_cut;
+
   /*! Cut off radius */
   float cut_off_radius;
 
@@ -166,18 +190,22 @@ INLINE static void sink_props_init_probabilities(
                   imf, imf->minimal_discrete_mass_Msun, mass_max) *
               Mtot;
 
-  message("Mass of the continuous part (in M_sun) : %g", Mc);
-  message("Mass of the discrete   part (in M_sun) : %g", Md);
-  message("Total IMF mass (in M_sun)              : %g", Mtot);
-  message("Number of stars in the continuous part : %g", Nc);
-  message("Number of stars in the discrete   part : %g", Nd);
+  if (engine_rank == 0) {
+    message("Mass of the continuous part (in M_sun) : %g", Mc);
+    message("Mass of the discrete   part (in M_sun) : %g", Md);
+    message("Total IMF mass (in M_sun)              : %g", Mtot);
+    message("Number of stars in the continuous part : %g", Nc);
+    message("Number of stars in the discrete   part : %g", Nd);
+  }
 
   /* if no continous part, return */
   if (Mc == 0) {
     imf->sink_Pc = 0;
     imf->stellar_particle_mass_Msun = 0;
-    message("probability of the continuous part    : %g", 0.);
-    message("probability of the discrete   part    : %g", 1.);
+    if (engine_rank == 0) {
+      message("probability of the continuous part    : %g", 0.);
+      message("probability of the discrete   part    : %g", 1.);
+    }
     return;
   }
 
@@ -186,8 +214,10 @@ INLINE static void sink_props_init_probabilities(
   double Pd = 1 - Pc;
   imf->sink_Pc = Pc;
 
-  message("probability of the continuous part     : %g", Pc);
-  message("probability of the discrete   part     : %g", Pd);
+  if (engine_rank == 0) {
+    message("probability of the continuous part     : %g", Pc);
+    message("probability of the discrete   part     : %g", Pd);
+  }
 }
 
 /**
@@ -200,13 +230,42 @@ INLINE static void sink_props_init_probabilities(
  * @param cosmo The cosmological model.
  * @param with_feedback Are we running with feedback?
  */
-INLINE static void sink_props_init(struct sink_props *sp,
-                                   struct feedback_props *fp,
-                                   const struct phys_const *phys_const,
-                                   const struct unit_system *us,
-                                   struct swift_params *params,
-                                   const struct cosmology *cosmo,
-                                   const int with_feedback) {
+INLINE static void sink_props_init(
+    struct sink_props *sp, struct feedback_props *fp,
+    const struct phys_const *phys_const, const struct unit_system *us,
+    struct swift_params *params, const struct hydro_props *hydro_props,
+    const struct cosmology *cosmo, const int with_feedback) {
+
+  /* Read in the basic neighbour search properties or default to the hydro
+     ones if the user did not provide any different values */
+
+  /* Kernel properties */
+  sp->eta_neighbours = parser_get_opt_param_float(
+      params, "Sinks:resolution_eta", hydro_props->eta_neighbours);
+
+  /* Tolerance for the smoothing length Newton-Raphson scheme */
+  sp->h_tolerance = parser_get_opt_param_float(params, "Sinks:h_tolerance",
+                                               hydro_props->h_tolerance);
+
+  /* Get derived properties */
+  sp->target_neighbours = pow_dimension(sp->eta_neighbours) * kernel_norm;
+  const float delta_eta = sp->eta_neighbours * (1.f + sp->h_tolerance);
+  sp->delta_neighbours =
+      (pow_dimension(delta_eta) - pow_dimension(sp->eta_neighbours)) *
+      kernel_norm;
+
+  /* Number of iterations to converge h */
+  sp->max_smoothing_iterations =
+      parser_get_opt_param_int(params, "Sinks:max_ghost_iterations",
+                               hydro_props->max_smoothing_iterations);
+
+  /* Time integration properties */
+  const float max_volume_change =
+      parser_get_opt_param_float(params, "Sinks:max_volume_change", -1);
+  if (max_volume_change == -1)
+    sp->log_max_h_change = hydro_props->log_max_h_change;
+  else
+    sp->log_max_h_change = logf(powf(max_volume_change, hydro_dimension_inv));
 
   /* If we do not run with feedback, abort and print an error */
   if (!with_feedback)
@@ -214,9 +273,20 @@ INLINE static void sink_props_init(struct sink_props *sp,
         "ERROR: Running with sink but without feedback. GEAR sink model needs "
         "to be run with --sink and --feedback");
 
-  /* Read the parameters from the parameter file */
-  sp->cut_off_radius =
-      parser_get_param_float(params, "GEARSink:cut_off_radius");
+  /* This property is used in all models to flag if we're using a fixed cutoff.
+   * If it is set to 1, we use a fixed r_cut (read in below) and don't
+   * (re)calculate h. If not, the code will use the variable h*kernel_gamma as a
+   * cutoff radius.
+   */
+  sp->use_fixed_r_cut =
+      parser_get_param_char(params, "GEARSink:use_fixed_cut_off_radius");
+
+  /* The property cut_off_radius is now only used in the GEAR model.
+   * It is ignored if use_fixed_r_cut is 0. */
+  if (sp->use_fixed_r_cut) {
+    sp->cut_off_radius =
+        parser_get_param_float(params, "GEARSink:cut_off_radius");
+  }
 
   sp->f_acc = parser_get_opt_param_float(params, "GEARSink:f_acc",
                                          sink_gear_f_acc_default);
@@ -353,53 +423,54 @@ INLINE static void sink_props_init(struct sink_props *sp,
     imf = &sm->imf;
     sink_props_init_probabilities(sp, imf, phys_const, 1);
   }
-
-  message("temperature_threshold                        = %g",
-          sp->temperature_threshold);
-  message("density_threshold                            = %g",
-          sp->density_threshold);
-  message("maximal_density_threshold                    = %g",
-          sp->maximal_density_threshold);
-  message("sink_minimal_mass (in M_sun)                 = %g",
-          sp->sink_minimal_mass_Msun);
-  message("stellar_particle_mass (in M_sun)             = %g",
-          sp->stellar_particle_mass_Msun);
-  message("minimal_discrete_mass (in M_sun)             = %g",
-          sp->minimal_discrete_mass_Msun);
-
-  message("stellar_particle_mass_first_stars (in M_sun) = %g",
-          sp->stellar_particle_mass_first_stars_Msun);
-  message("minimal_discrete_mass_first_stars (in M_sun) = %g",
-          sp->minimal_discrete_mass_first_stars_Msun);
-
-  /* Print information about the functionalities */
-  message("disable_sink_formation                       = %d",
-          sp->disable_sink_formation);
-  message("sink_formation_contracting_gas_criterion     = %d",
-          sp->sink_formation_contracting_gas_criterion);
-  message("sink_formation_smoothing_length_criterion    = %d",
-          sp->sink_formation_smoothing_length_criterion);
-  message("sink_formation_jeans_instability_criterion   = %d",
-          sp->sink_formation_jeans_instability_criterion);
-  message("sink_formation_bound_state_criterion         = %d",
-          sp->sink_formation_bound_state_criterion);
-  message("sink_formation_overlapping_sink_criterion    = %d",
-          sp->sink_formation_overlapping_sink_criterion);
-
-  /* Print timestep parameters information */
-  message("sink max_timestep_young                      = %e",
-          sp->max_time_step_young);
-  message("sink max_timestep_old                        = %e",
-          sp->max_time_step_old);
-  message("sink age_threshold from young to old         = %e",
-          sp->age_threshold);
-  message("sink age_threshold from old to unlimited     = %e",
-          sp->age_threshold_unlimited);
-  message("sink C_CFL                                   = %e",
-          sp->CFL_condition);
-  message("tolerance_SF_timestep                        = %e",
-          sp->tolerance_SF_timestep);
-  message("n_IMF                                        = %e", sp->n_IMF);
+  if (engine_rank == 0) {
+    message("temperature_threshold                        = %g",
+            sp->temperature_threshold);
+    message("density_threshold                            = %g",
+            sp->density_threshold);
+    message("maximal_density_threshold                    = %g",
+            sp->maximal_density_threshold);
+    message("sink_minimal_mass (in M_sun)                 = %g",
+            sp->sink_minimal_mass_Msun);
+    message("stellar_particle_mass (in M_sun)             = %g",
+            sp->stellar_particle_mass_Msun);
+    message("minimal_discrete_mass (in M_sun)             = %g",
+            sp->minimal_discrete_mass_Msun);
+
+    message("stellar_particle_mass_first_stars (in M_sun) = %g",
+            sp->stellar_particle_mass_first_stars_Msun);
+    message("minimal_discrete_mass_first_stars (in M_sun) = %g",
+            sp->minimal_discrete_mass_first_stars_Msun);
+
+    /* Print information about the functionalities */
+    message("disable_sink_formation                       = %d",
+            sp->disable_sink_formation);
+    message("sink_formation_contracting_gas_criterion     = %d",
+            sp->sink_formation_contracting_gas_criterion);
+    message("sink_formation_smoothing_length_criterion    = %d",
+            sp->sink_formation_smoothing_length_criterion);
+    message("sink_formation_jeans_instability_criterion   = %d",
+            sp->sink_formation_jeans_instability_criterion);
+    message("sink_formation_bound_state_criterion         = %d",
+            sp->sink_formation_bound_state_criterion);
+    message("sink_formation_overlapping_sink_criterion    = %d",
+            sp->sink_formation_overlapping_sink_criterion);
+
+    /* Print timestep parameters information */
+    message("sink max_timestep_young                      = %e",
+            sp->max_time_step_young);
+    message("sink max_timestep_old                        = %e",
+            sp->max_time_step_old);
+    message("sink age_threshold from young to old         = %e",
+            sp->age_threshold);
+    message("sink age_threshold from old to unlimited     = %e",
+            sp->age_threshold_unlimited);
+    message("sink C_CFL                                   = %e",
+            sp->CFL_condition);
+    message("tolerance_SF_timestep                        = %e",
+            sp->tolerance_SF_timestep);
+    message("n_IMF                                        = %e", sp->n_IMF);
+  }
 }
 
 /**
diff --git a/src/sink_debug.h b/src/sink_debug.h
index 2346711e7387ebe3e480c759e0322e3c1f407d8d..44b1937d435d5095f69f1c767520d8b300b93814 100644
--- a/src/sink_debug.h
+++ b/src/sink_debug.h
@@ -25,6 +25,8 @@
 /* Import the debug routines of the right sink definition */
 #if defined(SINK_NONE)
 #include "./sink/Default/sink_debug.h"
+#elif defined(SINK_BASIC)
+#include "./sink/Basic/sink_debug.h"
 #elif defined(SINK_GEAR)
 #include "./sink/GEAR/sink_debug.h"
 #else
diff --git a/src/sink_iact.h b/src/sink_iact.h
index d47b9971b76db5d54ccca92f3c0b911dd7126c63..4d5b6467f816fe1e6952f2a29b30f7f24ae20f02 100644
--- a/src/sink_iact.h
+++ b/src/sink_iact.h
@@ -25,6 +25,8 @@
 /* Select the correct sink model */
 #if defined(SINK_NONE)
 #include "./sink/Default/sink_iact.h"
+#elif defined(SINK_BASIC)
+#include "./sink/Basic/sink_iact.h"
 #elif defined(SINK_GEAR)
 #include "./sink/GEAR/sink_iact.h"
 #else
diff --git a/src/sink_io.h b/src/sink_io.h
index f5b0be176ca4dadf75bc825a9f2f001e1eef2883..685824f93bbd97351278f9ecc7c9988006a24fb8 100644
--- a/src/sink_io.h
+++ b/src/sink_io.h
@@ -24,6 +24,8 @@
 /* Load the correct sink type */
 #if defined(SINK_NONE)
 #include "./sink/Default/sink_io.h"
+#elif defined(SINK_BASIC)
+#include "./sink/Basic/sink_io.h"
 #elif defined(SINK_GEAR)
 #include "./sink/GEAR/sink_io.h"
 #else
diff --git a/src/sink_properties.h b/src/sink_properties.h
index 2ec9bd9d00fa09ef01792a42e3117096e65244ff..7700fa5395816857b87594c87cc97af578460edd 100644
--- a/src/sink_properties.h
+++ b/src/sink_properties.h
@@ -25,6 +25,8 @@
 /* Select the correct sink model */
 #if defined(SINK_NONE)
 #include "./sink/Default/sink_properties.h"
+#elif defined(SINK_BASIC)
+#include "./sink/Basic/sink_properties.h"
 #elif defined(SINK_GEAR)
 #include "./sink/GEAR/sink_properties.h"
 #else
diff --git a/src/sink_struct.h b/src/sink_struct.h
index 00e6380298182149d8666663c977c5be665ac11d..7164c1cb20cd6b544a4f03e4932c3cfc0900a696 100644
--- a/src/sink_struct.h
+++ b/src/sink_struct.h
@@ -33,6 +33,8 @@
 /* Import the right black holes definition */
 #if defined(SINK_NONE)
 #include "./sink/Default/sink_struct.h"
+#elif defined(SINK_BASIC)
+#include "./sink/Basic/sink_struct.h"
 #elif defined(SINK_GEAR)
 #include "./sink/GEAR/sink_struct.h"
 #else
diff --git a/src/space.c b/src/space.c
index b1370b7f756ae7519205418494885b8800023a98..1923496fb577e461d6ab31c6cedbeae0b4d57d65 100644
--- a/src/space.c
+++ b/src/space.c
@@ -75,6 +75,18 @@ int space_subdepth_diff_grav = space_subdepth_diff_grav_default;
 int space_maxsize = space_maxsize_default;
 int space_grid_split_threshold = space_grid_split_threshold_default;
 
+/* Recursion sizes */
+int space_recurse_size_self_hydro = space_recurse_size_self_hydro_default;
+int space_recurse_size_pair_hydro = space_recurse_size_pair_hydro_default;
+int space_recurse_size_self_stars = space_recurse_size_self_stars_default;
+int space_recurse_size_pair_stars = space_recurse_size_pair_stars_default;
+int space_recurse_size_self_black_holes =
+    space_recurse_size_self_black_holes_default;
+int space_recurse_size_pair_black_holes =
+    space_recurse_size_pair_black_holes_default;
+int space_recurse_size_self_sinks = space_recurse_size_self_sinks_default;
+int space_recurse_size_pair_sinks = space_recurse_size_pair_sinks_default;
+
 /*! Number of extra #part we allocate memory for per top-level cell */
 int space_extra_parts = space_extra_parts_default;
 
@@ -513,6 +525,7 @@ void space_getcells(struct space *s, int nr_cells, struct cell **cells,
     cells[j]->nodeID = -1;
     cells[j]->tpid = tpid;
     if (lock_init(&cells[j]->hydro.lock) != 0 ||
+        lock_init(&cells[j]->hydro.extra_sort_lock) != 0 ||
         lock_init(&cells[j]->grav.plock) != 0 ||
         lock_init(&cells[j]->grav.mlock) != 0 ||
         lock_init(&cells[j]->stars.lock) != 0 ||
@@ -1259,6 +1272,32 @@ void space_init(struct space *s, struct swift_params *params,
   space_subdepth_diff_grav =
       parser_get_opt_param_int(params, "Scheduler:cell_subdepth_diff_grav",
                                space_subdepth_diff_grav_default);
+
+  space_recurse_size_self_hydro =
+      parser_get_opt_param_int(params, "Scheduler:cell_recurse_size_self_hydro",
+                               space_recurse_size_self_hydro_default);
+  space_recurse_size_pair_hydro =
+      parser_get_opt_param_int(params, "Scheduler:cell_recurse_size_pair_hydro",
+                               space_recurse_size_pair_hydro_default);
+  space_recurse_size_self_stars =
+      parser_get_opt_param_int(params, "Scheduler:cell_recurse_size_self_stars",
+                               space_recurse_size_self_stars_default);
+  space_recurse_size_pair_stars =
+      parser_get_opt_param_int(params, "Scheduler:cell_recurse_size_pair_stars",
+                               space_recurse_size_pair_stars_default);
+  space_recurse_size_self_black_holes = parser_get_opt_param_int(
+      params, "Scheduler:cell_recurse_size_self_black_holes",
+      space_recurse_size_self_black_holes_default);
+  space_recurse_size_pair_black_holes = parser_get_opt_param_int(
+      params, "Scheduler:cell_recurse_size_pair_black_holes",
+      space_recurse_size_pair_black_holes_default);
+  space_recurse_size_self_sinks =
+      parser_get_opt_param_int(params, "Scheduler:cell_recurse_size_self_sinks",
+                               space_recurse_size_self_sinks_default);
+  space_recurse_size_pair_sinks =
+      parser_get_opt_param_int(params, "Scheduler:cell_recurse_size_pair_sinks",
+                               space_recurse_size_pair_sinks_default);
+
   space_extra_parts = parser_get_opt_param_int(
       params, "Scheduler:cell_extra_parts", space_extra_parts_default);
   space_extra_sparts = parser_get_opt_param_int(
@@ -2189,6 +2228,7 @@ void space_check_drift_point(struct space *s, integertime_t ti_drift,
   space_map_cells_pre(s, 1, cell_check_part_drift_point, &ti_drift);
   space_map_cells_pre(s, 1, cell_check_gpart_drift_point, &ti_drift);
   space_map_cells_pre(s, 1, cell_check_spart_drift_point, &ti_drift);
+  space_map_cells_pre(s, 1, cell_check_bpart_drift_point, &ti_drift);
   space_map_cells_pre(s, 1, cell_check_sink_drift_point, &ti_drift);
   if (multipole)
     space_map_cells_pre(s, 1, cell_check_multipole_drift_point, &ti_drift);
@@ -2487,6 +2527,23 @@ void space_after_snap_tracer(struct space *s, int verbose) {
   }
 }
 
+/**
+ * @brief Marks a top-level cell as having been updated by one of the
+ * time-step updating tasks.
+ */
+void space_mark_cell_as_updated(struct space *s, const struct cell *c) {
+
+#ifdef SWIFT_DEBUG_CHECKS
+  if (c != c->top) error("Function can only be called at the top level!");
+#endif
+
+  /* Get the offest into the top-level cell array */
+  const size_t delta = c - s->cells_top;
+
+  /* Mark as updated */
+  atomic_inc(&s->cells_top_updated[delta]);
+}
+
 /**
  * @brief Frees up the memory allocated for this #space
  */
@@ -2494,6 +2551,7 @@ void space_clean(struct space *s) {
 
   for (int i = 0; i < s->nr_cells; ++i) cell_clean(&s->cells_top[i]);
   swift_free("cells_top", s->cells_top);
+  swift_free("cells_top_updated", s->cells_top_updated);
   swift_free("multipoles_top", s->multipoles_top);
   swift_free("local_cells_top", s->local_cells_top);
   swift_free("local_cells_with_tasks_top", s->local_cells_with_tasks_top);
@@ -2578,6 +2636,30 @@ void space_struct_dump(struct space *s, FILE *stream) {
                        "space_extra_sparts", "space_extra_sparts");
   restart_write_blocks(&space_extra_bparts, sizeof(int), 1, stream,
                        "space_extra_bparts", "space_extra_bparts");
+  restart_write_blocks(&space_recurse_size_self_hydro, sizeof(int), 1, stream,
+                       "space_recurse_size_self_hydro",
+                       "space_recurse_size_self_hydro");
+  restart_write_blocks(&space_recurse_size_pair_hydro, sizeof(int), 1, stream,
+                       "space_recurse_size_pair_hydro",
+                       "space_recurse_size_pair_hydro");
+  restart_write_blocks(&space_recurse_size_self_stars, sizeof(int), 1, stream,
+                       "space_recurse_size_self_stars",
+                       "space_recurse_size_self_stars");
+  restart_write_blocks(&space_recurse_size_pair_stars, sizeof(int), 1, stream,
+                       "space_recurse_size_pair_stars",
+                       "space_recurse_size_pair_stars");
+  restart_write_blocks(&space_recurse_size_self_black_holes, sizeof(int), 1,
+                       stream, "space_recurse_size_self_black_holes",
+                       "space_recurse_size_self_black_holes");
+  restart_write_blocks(&space_recurse_size_pair_black_holes, sizeof(int), 1,
+                       stream, "space_recurse_size_pair_black_holes",
+                       "space_recurse_size_pair_black_holes");
+  restart_write_blocks(&space_recurse_size_self_sinks, sizeof(int), 1, stream,
+                       "space_recurse_size_self_sinks",
+                       "space_recurse_size_self_sinks");
+  restart_write_blocks(&space_recurse_size_pair_sinks, sizeof(int), 1, stream,
+                       "space_recurse_size_pair_sinks",
+                       "space_recurse_size_pair_sinks");
   restart_write_blocks(&space_expected_max_nr_strays, sizeof(int), 1, stream,
                        "space_expected_max_nr_strays",
                        "space_expected_max_nr_strays");
@@ -2675,6 +2757,22 @@ void space_struct_restore(struct space *s, FILE *stream) {
                       "space_extra_sparts");
   restart_read_blocks(&space_extra_bparts, sizeof(int), 1, stream, NULL,
                       "space_extra_bparts");
+  restart_read_blocks(&space_recurse_size_self_hydro, sizeof(int), 1, stream,
+                      NULL, "space_recurse_size_self_hydro");
+  restart_read_blocks(&space_recurse_size_pair_hydro, sizeof(int), 1, stream,
+                      NULL, "space_recurse_size_pair_hydro");
+  restart_read_blocks(&space_recurse_size_self_stars, sizeof(int), 1, stream,
+                      NULL, "space_recurse_size_self_stars");
+  restart_read_blocks(&space_recurse_size_pair_stars, sizeof(int), 1, stream,
+                      NULL, "space_recurse_size_pair_stars");
+  restart_read_blocks(&space_recurse_size_self_black_holes, sizeof(int), 1,
+                      stream, NULL, "space_recurse_size_self_black_holes");
+  restart_read_blocks(&space_recurse_size_pair_black_holes, sizeof(int), 1,
+                      stream, NULL, "space_recurse_size_pair_black_holes");
+  restart_read_blocks(&space_recurse_size_self_sinks, sizeof(int), 1, stream,
+                      NULL, "space_recurse_size_self_sinks");
+  restart_read_blocks(&space_recurse_size_pair_sinks, sizeof(int), 1, stream,
+                      NULL, "space_recurse_size_pair_sinks");
   restart_read_blocks(&space_expected_max_nr_strays, sizeof(int), 1, stream,
                       NULL, "space_expected_max_nr_strays");
   restart_read_blocks(&engine_max_parts_per_ghost, sizeof(int), 1, stream, NULL,
@@ -2696,6 +2794,7 @@ void space_struct_restore(struct space *s, FILE *stream) {
 
   /* Things that should be reconstructed in a rebuild. */
   s->cells_top = NULL;
+  s->cells_top_updated = NULL;
   s->cells_sub = NULL;
   s->multipoles_top = NULL;
   s->multipoles_sub = NULL;
diff --git a/src/space.h b/src/space.h
index 68c83d71674cf796c876f6d5f01afbf87250613a..c7537d9a8e184632a94892ee7c93e10d84dcd4c9 100644
--- a/src/space.h
+++ b/src/space.h
@@ -63,6 +63,14 @@ struct hydro_props;
 #define space_subsize_self_stars_default 32000
 #define space_subsize_pair_grav_default 256000000
 #define space_subsize_self_grav_default 32000
+#define space_recurse_size_self_hydro_default 100
+#define space_recurse_size_pair_hydro_default 100
+#define space_recurse_size_self_stars_default 100
+#define space_recurse_size_pair_stars_default 100
+#define space_recurse_size_self_black_holes_default 100
+#define space_recurse_size_pair_black_holes_default 100
+#define space_recurse_size_self_sinks_default 100
+#define space_recurse_size_pair_sinks_default 100
 #define space_subdepth_diff_grav_default 4
 #define space_max_top_level_cells_default 12
 #define space_stretch 1.10f
@@ -83,6 +91,14 @@ extern int space_subsize_self_stars;
 extern int space_subsize_pair_grav;
 extern int space_subsize_self_grav;
 extern int space_subdepth_diff_grav;
+extern int space_recurse_size_self_hydro;
+extern int space_recurse_size_pair_hydro;
+extern int space_recurse_size_self_stars;
+extern int space_recurse_size_pair_stars;
+extern int space_recurse_size_self_black_holes;
+extern int space_recurse_size_pair_black_holes;
+extern int space_recurse_size_self_sinks;
+extern int space_recurse_size_pair_sinks;
 extern int space_extra_parts;
 extern int space_extra_gparts;
 extern int space_extra_sparts;
@@ -339,6 +355,9 @@ struct space {
   /*! Structure that stores the zoom regions properties. */
   struct zoom_region_properties *zoom_props;
 
+  /*! Have the top-level cells' time-steps been updated? */
+  char *cells_top_updated;
+
 #ifdef WITH_MPI
 
   /*! Buffers for parts that we will receive from foreign cells. */
@@ -663,6 +682,7 @@ void space_init_sparts(struct space *s, int verbose);
 void space_init_bparts(struct space *s, int verbose);
 void space_init_sinks(struct space *s, int verbose);
 void space_after_snap_tracer(struct space *s, int verbose);
+void space_mark_cell_as_updated(struct space *s, const struct cell *c);
 void space_convert_quantities(struct space *s, int verbose);
 void space_convert_rt_quantities(struct space *s, int verbose);
 void space_post_init_parts(struct space *s, int verbose);
diff --git a/src/space_recycle.c b/src/space_recycle.c
index 3fe465eef1569ecf9b558eb14e287473f9415323..48689f14d716c48d605135cc2482467efce4d512 100644
--- a/src/space_recycle.c
+++ b/src/space_recycle.c
@@ -90,7 +90,9 @@ void space_rebuild_recycle_mapper(void *map_data, int num_elements,
                          multipole_rec_end);
     c->hydro.sorts = NULL;
     c->stars.sorts = NULL;
+#ifdef SWIFT_DEBUG_CHECKS
     c->nr_tasks = 0;
+#endif
     c->grav.nr_mm_tasks = 0;
     c->hydro.density = NULL;
     c->hydro.gradient = NULL;
@@ -258,6 +260,7 @@ void space_recycle(struct space *s, struct cell *c) {
   if (lock_destroy(&c->hydro.lock) != 0 || lock_destroy(&c->grav.plock) != 0 ||
       lock_destroy(&c->grav.mlock) != 0 || lock_destroy(&c->stars.lock) != 0 ||
       lock_destroy(&c->sinks.lock) != 0 ||
+      lock_destroy(&c->hydro.extra_sort_lock) != 0 ||
       lock_destroy(&c->sinks.sink_formation_lock) != 0 ||
       lock_destroy(&c->black_holes.lock) != 0 ||
       lock_destroy(&c->grav.star_formation_lock) != 0 ||
@@ -308,6 +311,7 @@ void space_recycle_list(struct space *s, struct cell *cell_list_begin,
         lock_destroy(&c->grav.mlock) != 0 ||
         lock_destroy(&c->stars.lock) != 0 ||
         lock_destroy(&c->sinks.lock) != 0 ||
+        lock_destroy(&c->hydro.extra_sort_lock) != 0 ||
         lock_destroy(&c->sinks.sink_formation_lock) != 0 ||
         lock_destroy(&c->black_holes.lock) != 0 ||
         lock_destroy(&c->stars.star_formation_lock) != 0 ||
diff --git a/src/space_regrid.c b/src/space_regrid.c
index d9763c8f192afb365b4e641ae270860181643dad..cddb719f0daed8072f5496a90ce2d5895f43b8c3 100644
--- a/src/space_regrid.c
+++ b/src/space_regrid.c
@@ -75,9 +75,8 @@ static float space_get_current_hmax(
         if (c->black_holes.h_max > h_max) {
           h_max = c->black_holes.h_max;
         }
-        /* Note: For sinks, r_cut <=> h*kernel_gamma */
-        if (c->sinks.r_cut_max > h_max * kernel_gamma) {
-          h_max = c->sinks.r_cut_max / kernel_gamma;
+        if (c->sinks.h_max > h_max) {
+          h_max = c->sinks.h_max;
         }
       }
 
@@ -94,10 +93,8 @@ static float space_get_current_hmax(
         if (c->nodeID == engine_rank && c->black_holes.h_max > h_max) {
           h_max = c->black_holes.h_max;
         }
-        /* Note: For sinks, r_cut <=> h*kernel_gamma */
-        if (c->nodeID == engine_rank &&
-            c->sinks.r_cut_max > h_max * kernel_gamma) {
-          h_max = c->sinks.r_cut_max / kernel_gamma;
+        if (c->nodeID == engine_rank && c->sinks.h_max > h_max) {
+          h_max = c->sinks.h_max;
         }
       }
 
@@ -113,7 +110,7 @@ static float space_get_current_hmax(
         if (s->bparts[k].h > h_max) h_max = s->bparts[k].h;
       }
       for (size_t k = 0; k < nr_sinks; k++) {
-        if (s->sinks[k].r_cut > h_max) h_max = s->sinks[k].r_cut / kernel_gamma;
+        if (s->sinks[k].h > h_max) h_max = s->sinks[k].h;
       }
     }
   }
@@ -283,6 +280,7 @@ static void space_prepare_cells(struct space *s, const int cdim[3]) {
     swift_free("local_cells_with_particles_top",
                s->local_cells_with_particles_top);
     swift_free("cells_top", s->cells_top);
+    swift_free("cells_top_updated", s->cells_top_updated);
     swift_free("multipoles_top", s->multipoles_top);
   }
 
@@ -335,6 +333,11 @@ void space_allocate_cells(struct space *s) {
     bzero(s->multipoles_top, s->nr_cells * sizeof(struct gravity_tensors));
   }
 
+  if (swift_memalign("cells_top_updated", (void **)&s->cells_top_updated,
+                     cell_align, s->nr_cells * sizeof(char)) != 0)
+    error("Failed to allocate top-level cells.");
+  bzero(s->cells_top_updated, s->nr_cells * sizeof(char));
+
   /* Allocate the indices of local cells */
   if (swift_memalign("local_cells_top", (void **)&s->local_cells_top,
                      SWIFT_STRUCT_ALIGNMENT, s->nr_cells * sizeof(int)) != 0)
@@ -368,6 +371,8 @@ void space_allocate_cells(struct space *s) {
   for (int k = 0; k < s->nr_cells; k++) {
     if (lock_init(&s->cells_top[k].hydro.lock) != 0)
       error("Failed to init spinlock for hydro.");
+    if (lock_init(&s->cells_top[k].hydro.extra_sort_lock) != 0)
+      error("Failed to init spinlock for hydro extra sort.");
     if (lock_init(&s->cells_top[k].grav.plock) != 0)
       error("Failed to init spinlock for gravity.");
     if (lock_init(&s->cells_top[k].grav.mlock) != 0)
@@ -413,6 +418,8 @@ static void uniform_construct_tl_cells(struct space *s,
         c->width[1] = s->width[1];
         c->width[2] = s->width[2];
         c->dmin = dmin;
+        c->h_min_allowed = c->dmin * 0.5 * (1. / kernel_gamma);
+        c->h_max_allowed = c->dmin * (1. / kernel_gamma);
         c->depth = 0;
         c->split = 0;
         c->hydro.count = 0;
@@ -429,6 +436,7 @@ static void uniform_construct_tl_cells(struct space *s,
         c->sinks.ti_old_part = ti_current;
         c->black_holes.ti_old_part = ti_current;
         c->grav.ti_old_multipole = ti_current;
+
 #ifdef WITH_MPI
         c->mpi.tag = -1;
         c->mpi.recv = NULL;
diff --git a/src/space_split.c b/src/space_split.c
index eab5b851822a0c3efe195f39eabff94b90f5632d..69283fbe96dc93774f3f390ab6f3b03ff3233a8d 100644
--- a/src/space_split.c
+++ b/src/space_split.c
@@ -209,6 +209,8 @@ void space_construct_progeny(struct space *s, struct cell *c,
     cp->width[1] = c->width[1] / 2;
     cp->width[2] = c->width[2] / 2;
     cp->dmin = c->dmin / 2;
+    cp->h_min_allowed = cp->dmin * 0.5 * (1. / kernel_gamma);
+    cp->h_max_allowed = cp->dmin * (1. / kernel_gamma);
     if (k & 4) cp->loc[0] += cp->width[0];
     if (k & 2) cp->loc[1] += cp->width[1];
     if (k & 1) cp->loc[2] += cp->width[2];
@@ -222,8 +224,8 @@ void space_construct_progeny(struct space *s, struct cell *c,
     cp->stars.h_max_active = 0.f;
     cp->stars.dx_max_part = 0.f;
     cp->stars.dx_max_sort = 0.f;
-    cp->sinks.r_cut_max = 0.f;
-    cp->sinks.r_cut_max_active = 0.f;
+    cp->sinks.h_max = 0.f;
+    cp->sinks.h_max_active = 0.f;
     cp->sinks.dx_max_part = 0.f;
     cp->black_holes.h_max = 0.f;
     cp->black_holes.h_max_active = 0.f;
@@ -449,6 +451,8 @@ static void space_populate_leaf_props(struct cell *c, struct space *s,
     if (part_is_active(&parts[k], e))
       h_max_active = max(h_max_active, parts[k].h);
 
+    cell_set_part_h_depth(&parts[k], c);
+
     /* Collect SFR from the particles after rebuilt */
     star_formation_logger_log_inactive_part(&parts[k], &xparts[k],
                                             &c->stars.sfh);
@@ -501,6 +505,8 @@ static void space_populate_leaf_props(struct cell *c, struct space *s,
     if (spart_is_active(&sparts[k], e))
       stars_h_max_active = max(stars_h_max_active, sparts[k].h);
 
+    cell_set_spart_h_depth(&sparts[k], c);
+
     /* Reset x_diff */
     sparts[k].x_diff[0] = 0.f;
     sparts[k].x_diff[1] = 0.f;
@@ -524,10 +530,12 @@ static void space_populate_leaf_props(struct cell *c, struct space *s,
     ti_sinks_end_min = min(ti_sinks_end_min, ti_end);
     ti_sinks_beg_max = max(ti_sinks_beg_max, ti_beg);
 
-    sinks_h_max = max(sinks_h_max, sinks[k].r_cut);
+    sinks_h_max = max(sinks_h_max, sinks[k].h);
 
     if (sink_is_active(&sinks[k], e))
-      sinks_h_max_active = max(sinks_h_max_active, sinks[k].r_cut);
+      sinks_h_max_active = max(sinks_h_max_active, sinks[k].h);
+
+    cell_set_sink_h_depth(&sinks[k], c);
 
     /* Reset x_diff */
     sinks[k].x_diff[0] = 0.f;
@@ -557,6 +565,8 @@ static void space_populate_leaf_props(struct cell *c, struct space *s,
     if (bpart_is_active(&bparts[k], e))
       black_holes_h_max_active = max(black_holes_h_max_active, bparts[k].h);
 
+    cell_set_bpart_h_depth(&bparts[k], c);
+
     /* Reset x_diff */
     bparts[k].x_diff[0] = 0.f;
     bparts[k].x_diff[1] = 0.f;
@@ -579,8 +589,8 @@ static void space_populate_leaf_props(struct cell *c, struct space *s,
   c->stars.h_max_active = stars_h_max_active;
   c->sinks.ti_end_min = ti_sinks_end_min;
   c->sinks.ti_beg_max = ti_sinks_beg_max;
-  c->sinks.r_cut_max = sinks_h_max;
-  c->sinks.r_cut_max_active = sinks_h_max_active;
+  c->sinks.h_max = sinks_h_max;
+  c->sinks.h_max_active = sinks_h_max_active;
   c->black_holes.ti_end_min = ti_black_holes_end_min;
   c->black_holes.ti_beg_max = ti_black_holes_beg_max;
   c->black_holes.h_max = black_holes_h_max;
@@ -760,9 +770,8 @@ void space_split_recursive(struct space *s, struct cell *c,
         black_holes_h_max = max(black_holes_h_max, cp->black_holes.h_max);
         black_holes_h_max_active =
             max(black_holes_h_max_active, cp->black_holes.h_max_active);
-        sinks_h_max = max(sinks_h_max, cp->sinks.r_cut_max);
-        sinks_h_max_active =
-            max(sinks_h_max_active, cp->sinks.r_cut_max_active);
+        sinks_h_max = max(sinks_h_max, cp->sinks.h_max);
+        sinks_h_max_active = max(sinks_h_max_active, cp->sinks.h_max_active);
 
         ti_hydro_end_min = min(ti_hydro_end_min, cp->hydro.ti_end_min);
         ti_hydro_beg_max = max(ti_hydro_beg_max, cp->hydro.ti_beg_max);
@@ -809,8 +818,8 @@ void space_split_recursive(struct space *s, struct cell *c,
     c->stars.h_max_active = stars_h_max_active;
     c->sinks.ti_end_min = ti_sinks_end_min;
     c->sinks.ti_beg_max = ti_sinks_beg_max;
-    c->sinks.r_cut_max = sinks_h_max;
-    c->sinks.r_cut_max_active = sinks_h_max_active;
+    c->sinks.h_max = sinks_h_max;
+    c->sinks.h_max_active = sinks_h_max_active;
     c->black_holes.ti_end_min = ti_black_holes_end_min;
     c->black_holes.ti_beg_max = ti_black_holes_beg_max;
     c->black_holes.h_max = black_holes_h_max;
@@ -831,7 +840,7 @@ void space_split_recursive(struct space *s, struct cell *c,
      * leaf and attach them to the leaf cell. */
     space_populate_leaf_props(c, s, ti_current, with_rt);
 
-    /* Construct the multipole and the centre of mass for this leaf. */
+    /* Construct the multipole and the centre of mass*/
     if (s->with_self_gravity) {
       space_construct_leaf_multipole(c, e);
     }
diff --git a/src/star_formation/EAGLE/star_formation_iact.h b/src/star_formation/EAGLE/star_formation_iact.h
index eb366c617b001ebfcfdb3f33a7163a8be67a14c6..a82c12a78950dd054e1642a0c835dd45115e4988 100644
--- a/src/star_formation/EAGLE/star_formation_iact.h
+++ b/src/star_formation/EAGLE/star_formation_iact.h
@@ -63,8 +63,8 @@ __attribute__((always_inline)) INLINE static void
 runner_iact_nonsym_star_formation(const float r2, const float dx[3],
                                   const float hi, const float hj,
                                   struct part *restrict pi,
-                                  const struct part *restrict pj, float a,
-                                  float H) {
+                                  const struct part *restrict pj, const float a,
+                                  const float H) {
 
   /* Nothing to do here. We do not need to compute any quantity in the hydro
      density loop for the EAGLE star formation model. */
diff --git a/src/star_formation/GEAR/star_formation_iact.h b/src/star_formation/GEAR/star_formation_iact.h
index 749b608068650a27cbe4c9a0ca4126d2740337f3..d6b51dd0e4b4fc811bc7eaa21d35f2cf94c4a864 100644
--- a/src/star_formation/GEAR/star_formation_iact.h
+++ b/src/star_formation/GEAR/star_formation_iact.h
@@ -38,8 +38,9 @@
  * @param H Current Hubble parameter.
  */
 __attribute__((always_inline)) INLINE static void runner_iact_star_formation(
-    float r2, const float *dx, float hi, float hj, struct part *restrict pi,
-    struct part *restrict pj, float a, float H) {}
+    const float r2, const float dx[3], const float hi, const float hj,
+    struct part *restrict pi, struct part *restrict pj, const float a,
+    const float H) {}
 
 /**
  * @brief do star_formation computation after the runner_iact_density (non
@@ -55,9 +56,10 @@ __attribute__((always_inline)) INLINE static void runner_iact_star_formation(
  * @param H Current Hubble parameter.
  */
 __attribute__((always_inline)) INLINE static void
-runner_iact_nonsym_star_formation(float r2, const float *dx, float hi, float hj,
+runner_iact_nonsym_star_formation(const float r2, const float dx[3],
+                                  const float hi, const float hj,
                                   struct part *restrict pi,
-                                  const struct part *restrict pj, float a,
-                                  float H) {}
+                                  const struct part *restrict pj, const float a,
+                                  const float H) {}
 
 #endif /* SWIFT_GEAR_STAR_FORMATION_IACT_H */
diff --git a/src/star_formation/QLA/star_formation_iact.h b/src/star_formation/QLA/star_formation_iact.h
index 3dfe77fc1531ddca236678c4dc2c07466942d9e1..f33873bf7485676183bfc567c587908f8c782d82 100644
--- a/src/star_formation/QLA/star_formation_iact.h
+++ b/src/star_formation/QLA/star_formation_iact.h
@@ -38,8 +38,9 @@
  * @param H Current Hubble parameter.
  */
 __attribute__((always_inline)) INLINE static void runner_iact_star_formation(
-    float r2, const float *dx, float hi, float hj, struct part *restrict pi,
-    struct part *restrict pj, float a, float H) {
+    const float r2, const float dx[3], const float hi, const float hj,
+    struct part *restrict pi, struct part *restrict pj, const float a,
+    const float H) {
 
   /* Nothing to do here. We do not need to compute any quantity in the hydro
      density loop for the QLA star formation model. */
@@ -59,10 +60,11 @@ __attribute__((always_inline)) INLINE static void runner_iact_star_formation(
  * @param H Current Hubble parameter.
  */
 __attribute__((always_inline)) INLINE static void
-runner_iact_nonsym_star_formation(float r2, const float *dx, float hi, float hj,
+runner_iact_nonsym_star_formation(const float r2, const float dx[3],
+                                  const float hi, const float hj,
                                   struct part *restrict pi,
-                                  const struct part *restrict pj, float a,
-                                  float H) {
+                                  const struct part *restrict pj, const float a,
+                                  const float H) {
 
   /* Nothing to do here. We do not need to compute any quantity in the hydro
      density loop for the QLA star formation model. */
diff --git a/src/star_formation/none/star_formation_iact.h b/src/star_formation/none/star_formation_iact.h
index 6731f1a24a96fe6881e04e088673c2ec159bd82e..461cf8f3362efd60fa4f41c796194d6ace072a9c 100644
--- a/src/star_formation/none/star_formation_iact.h
+++ b/src/star_formation/none/star_formation_iact.h
@@ -38,8 +38,9 @@
  * @param H Current Hubble parameter.
  */
 __attribute__((always_inline)) INLINE static void runner_iact_star_formation(
-    float r2, const float *dx, float hi, float hj, struct part *restrict pi,
-    struct part *restrict pj, float a, float H) {}
+    const float r2, const float dx[3], const float hi, const float hj,
+    struct part *restrict pi, struct part *restrict pj, const float a,
+    const float H) {}
 
 /**
  * @brief do star_formation computation after the runner_iact_density (non
@@ -55,9 +56,10 @@ __attribute__((always_inline)) INLINE static void runner_iact_star_formation(
  * @param H Current Hubble parameter.
  */
 __attribute__((always_inline)) INLINE static void
-runner_iact_nonsym_star_formation(float r2, const float *dx, float hi, float hj,
+runner_iact_nonsym_star_formation(const float r2, const float dx[3],
+                                  const float hi, const float hj,
                                   struct part *restrict pi,
-                                  const struct part *restrict pj, float a,
-                                  float H) {}
+                                  const struct part *restrict pj, const float a,
+                                  const float H) {}
 
 #endif /* SWIFT_NONE_STAR_FORMATION_IACT_H */
diff --git a/src/stars/Basic/stars_iact.h b/src/stars/Basic/stars_iact.h
index 90e9a0549ac440ff8819340b14c9af0610215ad8..dba76d5f9b9591c98add428c22f6a08b4c4e2898 100644
--- a/src/stars/Basic/stars_iact.h
+++ b/src/stars/Basic/stars_iact.h
@@ -33,7 +33,7 @@
  * @param H Current Hubble parameter.
  */
 __attribute__((always_inline)) INLINE static void
-runner_iact_nonsym_stars_density(const float r2, const float *dx,
+runner_iact_nonsym_stars_density(const float r2, const float dx[3],
                                  const float hi, const float hj,
                                  struct spart *restrict si,
                                  const struct part *restrict pj, const float a,
@@ -76,7 +76,7 @@ runner_iact_nonsym_stars_density(const float r2, const float *dx,
  * @param H Current Hubble parameter.
  */
 __attribute__((always_inline)) INLINE static void
-runner_iact_nonsym_stars_feedback(const float r2, const float *dx,
+runner_iact_nonsym_stars_feedback(const float r2, const float dx[3],
                                   const float hi, const float hj,
                                   struct spart *restrict si,
                                   struct part *restrict pj, const float a,
diff --git a/src/stars/Basic/stars_part.h b/src/stars/Basic/stars_part.h
index 713fe0e281c65ad4e484e42682f83c16b88b1cda..283fe9d6fcc3884694580ee49383184bf9c09a82 100644
--- a/src/stars/Basic/stars_part.h
+++ b/src/stars/Basic/stars_part.h
@@ -81,6 +81,9 @@ struct spart {
   /*! Particle time bin */
   timebin_t time_bin;
 
+  /*! Tree-depth at which size / 2 <= h * gamma < size */
+  char depth_h;
+
   /*! Star formation struct */
   struct star_formation_spart_data sf_data;
 
diff --git a/src/stars/EAGLE/stars_part.h b/src/stars/EAGLE/stars_part.h
index 22091ecc4f6c43928993b6eb8ff43ed9d9e7cd1e..642648f6254c1bed076bf68b19625f1f8c79e7ab 100644
--- a/src/stars/EAGLE/stars_part.h
+++ b/src/stars/EAGLE/stars_part.h
@@ -124,6 +124,9 @@ struct spart {
   /*! Particle time bin */
   timebin_t time_bin;
 
+  /*! Tree-depth at which size / 2 <= h * gamma < size */
+  char depth_h;
+
   /*! Number of time-steps since the last enrichment step */
   char count_since_last_enrichment;
 
diff --git a/src/stars/GEAR/stars_iact.h b/src/stars/GEAR/stars_iact.h
index 8c4f4e1196a5bd12f0e6f2cb7dc2fc7e88f23c87..456aeba25d534ebd3f993aa9fd7ad7bb9d737255 100644
--- a/src/stars/GEAR/stars_iact.h
+++ b/src/stars/GEAR/stars_iact.h
@@ -32,7 +32,7 @@
  * @param H Current Hubble parameter.
  */
 __attribute__((always_inline)) INLINE static void
-runner_iact_nonsym_stars_density(const float r2, const float *dx,
+runner_iact_nonsym_stars_density(const float r2, const float dx[3],
                                  const float hi, const float hj,
                                  struct spart *restrict si,
                                  const struct part *restrict pj, const float a,
@@ -76,7 +76,7 @@ runner_iact_nonsym_stars_density(const float r2, const float *dx,
  * @param H Current Hubble parameter.
  */
 __attribute__((always_inline)) INLINE static void
-runner_iact_nonsym_stars_feedback(const float r2, const float *dx,
+runner_iact_nonsym_stars_feedback(const float r2, const float dx[3],
                                   const float hi, const float hj,
                                   struct spart *restrict si,
                                   struct part *restrict pj, const float a,
diff --git a/src/stars/GEAR/stars_part.h b/src/stars/GEAR/stars_part.h
index 5d8ee3cd5f6db5b48d76b5b664ec73b22ee34be2..9be7ce07cbb70c63406238dbb565ff9c29a3e5c1 100644
--- a/src/stars/GEAR/stars_part.h
+++ b/src/stars/GEAR/stars_part.h
@@ -110,6 +110,9 @@ struct spart {
   /*! Particle time bin */
   timebin_t time_bin;
 
+  /*! Tree-depth at which size / 2 <= h * gamma < size */
+  char depth_h;
+
 #ifdef SWIFT_DEBUG_CHECKS
 
   /* Time of the last drift */
diff --git a/src/stars/None/stars_iact.h b/src/stars/None/stars_iact.h
index 266e7897db17ff7632972915de609b8436b6674d..90c04f40d6c5d55488fb7fc132819ce8af3fa37c 100644
--- a/src/stars/None/stars_iact.h
+++ b/src/stars/None/stars_iact.h
@@ -34,7 +34,7 @@
  * @param H Current Hubble parameter.
  */
 __attribute__((always_inline)) INLINE static void
-runner_iact_nonsym_stars_density(const float r2, const float *dx,
+runner_iact_nonsym_stars_density(const float r2, const float dx[3],
                                  const float hi, const float hj,
                                  struct spart *restrict si,
                                  const struct part *restrict pj, const float a,
@@ -55,7 +55,7 @@ runner_iact_nonsym_stars_density(const float r2, const float *dx,
  * @param H Current Hubble parameter.
  */
 __attribute__((always_inline)) INLINE static void
-runner_iact_nonsym_stars_feedback(const float r2, const float *dx,
+runner_iact_nonsym_stars_feedback(const float r2, const float dx[3],
                                   const float hi, const float hj,
                                   struct spart *restrict si,
                                   struct part *restrict pj, const float a,
diff --git a/src/stars/None/stars_part.h b/src/stars/None/stars_part.h
index 2400407ce25a60b504c31bb782730a7dfe6521fc..bf1ae8ee1f2c30416fd0db6585be6fedff2149be 100644
--- a/src/stars/None/stars_part.h
+++ b/src/stars/None/stars_part.h
@@ -58,6 +58,9 @@ struct spart {
   /*! Particle time bin */
   timebin_t time_bin;
 
+  /*! Tree-depth at which size / 2 <= h * gamma < size */
+  char depth_h;
+
   /*! Tracer structure */
   struct tracers_spart_data tracers_data;
 
diff --git a/src/tools.c b/src/tools.c
index 03c4e29325e1c71859cc30717812d0335bc05f4b..65d7d976ccc060e8a2111acf88a3a3cc862811bf 100644
--- a/src/tools.c
+++ b/src/tools.c
@@ -243,8 +243,7 @@ void pairs_all_density(struct runner *r, struct cell *ci, struct cell *cj) {
         runner_iact_nonsym_chemistry(r2, dx, hi, pj->h, pi, pj, a, H);
         runner_iact_nonsym_pressure_floor(r2, dx, hi, pj->h, pi, pj, a, H);
         runner_iact_nonsym_star_formation(r2, dx, hi, pj->h, pi, pj, a, H);
-        runner_iact_nonsym_sink(r2, dx, hi, pj->h, pi, pj, a, H,
-                                e->sink_properties->cut_off_radius);
+        runner_iact_nonsym_sink(r2, dx, hi, pj->h, pi, pj, a, H);
       }
     }
   }
@@ -279,8 +278,7 @@ void pairs_all_density(struct runner *r, struct cell *ci, struct cell *cj) {
         runner_iact_nonsym_chemistry(r2, dx, hj, pi->h, pj, pi, a, H);
         runner_iact_nonsym_pressure_floor(r2, dx, hj, pi->h, pj, pi, a, H);
         runner_iact_nonsym_star_formation(r2, dx, hj, pi->h, pj, pi, a, H);
-        runner_iact_nonsym_sink(r2, dx, hj, pi->h, pj, pi, a, H,
-                                e->sink_properties->cut_off_radius);
+        runner_iact_nonsym_sink(r2, dx, hj, pi->h, pj, pi, a, H);
       }
     }
   }
@@ -559,8 +557,7 @@ void self_all_density(struct runner *r, struct cell *ci) {
         runner_iact_nonsym_chemistry(r2, dxi, hi, hj, pi, pj, a, H);
         runner_iact_nonsym_pressure_floor(r2, dxi, hi, hj, pi, pj, a, H);
         runner_iact_nonsym_star_formation(r2, dxi, hi, hj, pi, pj, a, H);
-        runner_iact_nonsym_sink(r2, dxi, hi, hj, pi, pj, a, H,
-                                e->sink_properties->cut_off_radius);
+        runner_iact_nonsym_sink(r2, dxi, hi, hj, pi, pj, a, H);
       }
 
       /* Hit or miss? */
@@ -575,8 +572,7 @@ void self_all_density(struct runner *r, struct cell *ci) {
         runner_iact_nonsym_chemistry(r2, dxi, hj, hi, pj, pi, a, H);
         runner_iact_nonsym_pressure_floor(r2, dxi, hj, hi, pj, pi, a, H);
         runner_iact_nonsym_star_formation(r2, dxi, hj, hi, pj, pi, a, H);
-        runner_iact_nonsym_sink(r2, dxi, hj, hi, pj, pi, a, H,
-                                e->sink_properties->cut_off_radius);
+        runner_iact_nonsym_sink(r2, dxi, hj, hi, pj, pi, a, H);
       }
     }
   }
diff --git a/swift.c b/swift.c
index 5805ce4e1ddaa316dd837746df701effaa25c392..4f9c58269fd86861cd08e249f959301bafe4d376 100644
--- a/swift.c
+++ b/swift.c
@@ -843,7 +843,7 @@ int main(int argc, char *argv[]) {
     error("Cannot reconstruct m-poles every step over MPI (yet).");
 #endif
 
-    /* Temporary early aborts for modes not supported with hand-vec. */
+  /* Temporary early aborts for modes not supported with hand-vec. */
 #if defined(WITH_VECTORIZATION) && defined(GADGET2_SPH) && \
     !defined(CHEMISTRY_NONE)
   error(
@@ -1177,11 +1177,11 @@ int main(int argc, char *argv[]) {
     /* Initialise the sink properties */
     if (with_sinks) {
       sink_props_init(&sink_properties, &feedback_properties, &prog_const, &us,
-                      params, &cosmo, with_feedback);
+                      params, &hydro_properties, &cosmo, with_feedback);
     } else
       bzero(&sink_properties, sizeof(struct sink_props));
 
-      /* Initialise the cooling function properties */
+    /* Initialise the cooling function properties */
 #ifdef COOLING_NONE
     if (with_cooling) {
       error(
diff --git a/tests/test125cells.c b/tests/test125cells.c
index 1425104485305a299317d79c4cbcc28b1e15170a..001f6feb7197f5457e248c717cf376647d290568 100644
--- a/tests/test125cells.c
+++ b/tests/test125cells.c
@@ -660,7 +660,7 @@ int main(int argc, char *argv[]) {
 
     /* First, sort stuff */
     for (int j = 0; j < 125; ++j)
-      runner_do_hydro_sort(&runner, cells[j], 0x1FFF, 0, 0, 0);
+      runner_do_hydro_sort(&runner, cells[j], 0x1FFF, 0, 0, 0, 0);
 
       /* Do the density calculation */
 
diff --git a/tests/test27cells.c b/tests/test27cells.c
index 46dfe5fc1dde3321dd919a7cbf6ee7f2dc0aa861..3cb199d0b825c4f6fd20c65f5cc0ec26097f9466 100644
--- a/tests/test27cells.c
+++ b/tests/test27cells.c
@@ -517,7 +517,7 @@ int main(int argc, char *argv[]) {
 
         runner_do_drift_part(&runner, cells[i * 9 + j * 3 + k], 0);
 
-        runner_do_hydro_sort(&runner, cells[i * 9 + j * 3 + k], 0x1FFF, 0, 0,
+        runner_do_hydro_sort(&runner, cells[i * 9 + j * 3 + k], 0x1FFF, 0, 0, 0,
                              0);
       }
     }
diff --git a/tests/test27cellsStars.c b/tests/test27cellsStars.c
index 7785cdaed65e1415626a8f997c15bed1500ad80e..28a3c66d5dcb1ed352e886f9360cf87355ebcd55 100644
--- a/tests/test27cellsStars.c
+++ b/tests/test27cellsStars.c
@@ -430,7 +430,7 @@ int main(int argc, char *argv[]) {
         runner_do_drift_part(&runner, cells[i * 9 + j * 3 + k], 0);
         runner_do_drift_spart(&runner, cells[i * 9 + j * 3 + k], 0);
 
-        runner_do_hydro_sort(&runner, cells[i * 9 + j * 3 + k], 0x1FFF, 0, 0,
+        runner_do_hydro_sort(&runner, cells[i * 9 + j * 3 + k], 0x1FFF, 0, 0, 0,
                              0);
         runner_do_stars_sort(&runner, cells[i * 9 + j * 3 + k], 0x1FFF, 0, 0);
       }
diff --git a/tests/testActivePair.c b/tests/testActivePair.c
index 25c229d591e9c73953c2b1c85d108ceb662d0d73..afdda45480926066d9cc1154ee753fe25f6bb317 100644
--- a/tests/testActivePair.c
+++ b/tests/testActivePair.c
@@ -362,8 +362,8 @@ void test_pair_interactions(struct runner *runner, struct cell **ci,
 
   const struct engine *e = runner->e;
 
-  runner_do_hydro_sort(runner, *ci, 0x1FFF, 0, 0, 0);
-  runner_do_hydro_sort(runner, *cj, 0x1FFF, 0, 0, 0);
+  runner_do_hydro_sort(runner, *ci, 0x1FFF, 0, 0, 0, 0);
+  runner_do_hydro_sort(runner, *cj, 0x1FFF, 0, 0, 0, 0);
 
   /* Zero the fields */
   init(*ci, e->cosmology, e->hydro_properties, e->pressure_floor_props);
diff --git a/tests/testPeriodicBC.c b/tests/testPeriodicBC.c
index 4d540c0594569de62abe86d4275160ed495f35ce..7a99d44d0830e7acd51b0d842523503ed7177266 100644
--- a/tests/testPeriodicBC.c
+++ b/tests/testPeriodicBC.c
@@ -536,7 +536,7 @@ int main(int argc, char *argv[]) {
         runner_do_drift_part(runner, cells[i * (dim * dim) + j * dim + k], 0);
 
         runner_do_hydro_sort(runner, cells[i * (dim * dim) + j * dim + k],
-                             0x1FFF, 0, 0, 0);
+                             0x1FFF, 0, 0, 0, 0);
       }
     }
   }
diff --git a/tests/testSymmetry.c b/tests/testSymmetry.c
index e423b7dd01c1ba84fd404e8204b0a9b02bc10acd..e3bb175d2cf198f757f178c27195fc0ac24f9120 100644
--- a/tests/testSymmetry.c
+++ b/tests/testSymmetry.c
@@ -45,8 +45,6 @@ void test(void) {
   const float mu_0 = 4. * M_PI;
   const integertime_t ti_current = 1;
   const double time_base = 1e-5;
-  /* sink cut-off radius */
-  const float rcut = 0.5;
 
   /* Create two random particles (don't do this at home !) */
   struct part pi, pj;
@@ -142,7 +140,7 @@ void test(void) {
   runner_iact_chemistry(r2, dx, pi.h, pj.h, &pi, &pj, a, H);
   runner_iact_pressure_floor(r2, dx, pi.h, pj.h, &pi, &pj, a, H);
   runner_iact_star_formation(r2, dx, pi.h, pj.h, &pi, &pj, a, H);
-  runner_iact_sink(r2, dx, pi.h, pj.h, &pi, &pj, a, H, rcut);
+  runner_iact_sink(r2, dx, pi.h, pj.h, &pi, &pj, a, H);
 
   /* Call the non-symmetric version */
   runner_iact_nonsym_density(r2, dx, pi2.h, pj2.h, &pi2, &pj2, a, H);
@@ -150,7 +148,7 @@ void test(void) {
   runner_iact_nonsym_chemistry(r2, dx, pi2.h, pj2.h, &pi2, &pj2, a, H);
   runner_iact_nonsym_pressure_floor(r2, dx, pi2.h, pj2.h, &pi2, &pj2, a, H);
   runner_iact_nonsym_star_formation(r2, dx, pi2.h, pj2.h, &pi2, &pj2, a, H);
-  runner_iact_nonsym_sink(r2, dx, pi2.h, pj2.h, &pi2, &pj2, a, H, rcut);
+  runner_iact_nonsym_sink(r2, dx, pi2.h, pj2.h, &pi2, &pj2, a, H);
   dx[0] = -dx[0];
   dx[1] = -dx[1];
   dx[2] = -dx[2];
@@ -159,7 +157,7 @@ void test(void) {
   runner_iact_nonsym_chemistry(r2, dx, pj2.h, pi2.h, &pj2, &pi2, a, H);
   runner_iact_nonsym_pressure_floor(r2, dx, pj2.h, pi2.h, &pj2, &pi2, a, H);
   runner_iact_nonsym_star_formation(r2, dx, pj2.h, pi2.h, &pj2, &pi2, a, H);
-  runner_iact_nonsym_sink(r2, dx, pj2.h, pi2.h, &pj2, &pi2, a, H, rcut);
+  runner_iact_nonsym_sink(r2, dx, pj2.h, pi2.h, &pj2, &pi2, a, H);
 
   /* Check that the particles are the same */
   i_not_ok = memcmp(&pi, &pi2, sizeof(struct part));
diff --git a/tools/analyse_runtime.py b/tools/analyse_runtime.py
index bfb501d814eb32c5a571a59ed0d789f944659633..e80015972aa42118b464e0a455354d0b0ea43935 100755
--- a/tools/analyse_runtime.py
+++ b/tools/analyse_runtime.py
@@ -152,7 +152,7 @@ for i in range(num_files):
         for i in range(len(tasks)):
 
             # Extract the different blocks
-            if re.search("scheduler_report_task_times: \*\*\*  ", line):
+            if re.search("scheduler_report_task_times: \\*\\*\\*  ", line):
                 if re.search("%s" % tasks[i], line):
                     counts_tasks[i] += 1.0
                     times_tasks[i] += float(
@@ -205,7 +205,7 @@ important_is_rebuild = [0]
 important_is_fof = [0]
 important_is_VR = [0]
 important_is_mesh = [0]
-important_labels = ["Others (all below %.1f\%%)" % (threshold * 100)]
+important_labels = ["Others (all below %.1f\\%%)" % (threshold * 100)]
 need_print = True
 print("Time spent in the different code sections:")
 for i in range(len(labels)):
diff --git a/tools/timed_functions.py b/tools/timed_functions.py
index d4ad20fae9d95219df6bf573b7154f6b49a985e2..f729039edd9fa3eec91ec9831535a5e69cf6073f 100644
--- a/tools/timed_functions.py
+++ b/tools/timed_functions.py
@@ -35,15 +35,16 @@ labels = [
     ["engine_unskip:", 0],
     ["engine_unskip_timestep_communications:", 0],
     ["engine_collect_end_of_step:", 0],
-    ["engine_launch: \(tasks\)", 0],
-    ["engine_launch: \(timesteps\)", 0],
-    ["engine_launch: \(cycles\)", 0],
+    ["engine_launch: \\(tasks\\)", 0],
+    ["engine_launch: \\(timesteps\\)", 0],
+    ["engine_launch: \\(cycles\\)", 0],
+    ["engine_launch: \\(tend\\)", 0],
+    ["Gathering and activating tend", 0],
     ["writing particle properties", 0],
     ["engine_repartition:", 0],
     ["engine_exchange_cells:", 1],
     ["Dumping restart files", 0],
     ["engine_print_stats:", 0],
-    ["engine_marktasks:", 1],
     ["Reading initial conditions", 0],
     ["engine_print_task_counts:", 0],
     ["engine_drift_top_multipoles:", 0],
@@ -63,12 +64,16 @@ labels = [
     ["VR Collecting particle info", 3],
     ["VR Invocation of velociraptor", 3],
     ["VR Copying group information back", 3],
+    ["calc_all_power_spectra:", 0],
     ["fof_allocate:", 2],
     ["engine_make_fof_tasks:", 2],
     ["engine_activate_fof_tasks:", 2],
-    ["fof_search_tree:", 2],
-    ["engine_launch: \(fof\)", 2],
-    ["engine_launch: \(fof comms\)", 2],
+    ["fof_search_foreign_cells\\(\\)", 2],
+    ["link_foreign_fragmens\\(\\)", 2],
+    ["engine_launch: \\(fof\\)", 2],
+    ["engine_launch: \\(fof comms\\)", 2],
+    ["fof_compute_group_props:", 2],
+    ["fof_compute_local_sizes:", 2],
     ["do_line_of_sight:", 0],
     ["csds_log_all_particles:", 0],
     ["csds_ensure_size:", 0],