diff --git a/src/simutrans/obj/way/schiene.cc b/src/simutrans/obj/way/schiene.cc
index acee945ff..fcc6e7f4d 100644
--- a/src/simutrans/obj/way/schiene.cc
+++ b/src/simutrans/obj/way/schiene.cc
@@ -26,7 +26,10 @@ bool schiene_t::show_reservations = false;
 
 schiene_t::schiene_t() : weg_t()
 {
-	reserved = convoihandle_t();
+	reserved      = convoihandle_t();
+	reserved_dir  = ribi_t::none;
+	reserved2     = convoihandle_t();
+	reserved2_dir = ribi_t::none;
 
 	if (schiene_t::default_schiene) {
 		set_desc(schiene_t::default_schiene);
@@ -39,7 +42,10 @@ schiene_t::schiene_t() : weg_t()
 
 schiene_t::schiene_t(loadsave_t *file) : weg_t()
 {
-	reserved = convoihandle_t();
+	reserved      = convoihandle_t();
+	reserved_dir  = ribi_t::none;
+	reserved2     = convoihandle_t();
+	reserved2_dir = ribi_t::none;
 	rdwr(file);
 }
 
@@ -51,6 +57,9 @@ void schiene_t::cleanup(player_t *)
 		set_ribi(ribi_t::none);
 		reserved->suche_neue_route();
 	}
+	if(reserved2.is_bound()) {
+		reserved2->suche_neue_route();
+	}
 }
 
 
@@ -71,16 +80,49 @@ void schiene_t::info(cbuffer_t & buf) const
 		reserved->open_info_window();
 #endif
 	}
+	if(reserved2.is_bound()) {
+		const char* reserve_text = translator::translate("\nis co-reserved by:");
+		if (reserve_text[0] == '\n') {
+			reserve_text++;
+		}
+		buf.append(reserve_text);
+		buf.append(reserved2->get_name());
+		buf.append("\n");
+	}
 }
 
 
 /**
  * true, if this rail can be reserved
  */
-bool schiene_t::reserve(convoihandle_t c, ribi_t::ribi dir  )
+bool schiene_t::reserve(convoihandle_t c, ribi_t::ribi dir)
 {
-	if(can_reserve(c)) {
-		reserved = c;
+	if(c == reserved) {
+		// Allow direction restoration when reserved_dir is still none (e.g. after
+		// save/load before reserve_route() has run).  Do NOT overwrite a valid
+		// corner_set: enter_tile passes get_direction() which may be a single ribi
+		// that would corrupt the PBS corner_set.
+		if(reserved_dir == ribi_t::none  &&  dir != ribi_t::none) {
+			reserved_dir = dir;
+		}
+		if(schiene_t::show_reservations) {
+			set_flag( obj_t::dirty );
+		}
+		return true;
+	}
+	if(c == reserved2) {
+		if(reserved2_dir == ribi_t::none  &&  dir != ribi_t::none) {
+			reserved2_dir = dir;
+		}
+		if(schiene_t::show_reservations) {
+			set_flag( obj_t::dirty );
+		}
+		return true;
+	}
+	if(!reserved.is_bound()) {
+		// fresh reservation
+		reserved     = c;
+		reserved_dir = dir;
 		/* for threeway and fourway switches we may need to alter graphic, if
 		 * direction is a diagonal (i.e. on the switching part)
 		 * and there are switching graphics
@@ -88,7 +130,9 @@ bool schiene_t::reserve(convoihandle_t c, ribi_t::ribi dir  )
 		if(  ribi_t::is_threeway(get_ribi_unmasked())  &&  ribi_t::is_bend(dir)  &&  get_desc()->has_switch_image()  ) {
 			mark_image_dirty( get_image(), 0 );
 			mark_image_dirty( get_front_image(), 0 );
-			set_switched(dir == ribi_t::northeast || dir == ribi_t::southwest);
+			// dir is now corner_set (entry_border|exit_border); SE/NW corners match the
+			// same physical switch variant that NE/SW matched under the old ribi_type(prev,next) formula.
+			set_switched(dir == ribi_t::southeast || dir == ribi_t::northwest);
 			set_images(image_switch, get_ribi_unmasked(), is_snow(), has_switched() );
 			set_flag( obj_t::dirty );
 		}
@@ -97,7 +141,15 @@ bool schiene_t::reserve(convoihandle_t c, ribi_t::ribi dir  )
 		}
 		return true;
 	}
-	// reserve anyway ...
+	// tile is reserved by a different convoy — try co-reservation for non-crossing bends
+	if(!reserved2.is_bound()  &&  can_co_reserve_dirs(reserved_dir, dir)) {
+		reserved2     = c;
+		reserved2_dir = dir;
+		if(schiene_t::show_reservations) {
+			set_flag( obj_t::dirty );
+		}
+		return true;
+	}
 	return false;
 }
 
@@ -106,11 +158,29 @@ bool schiene_t::reserve(convoihandle_t c, ribi_t::ribi dir  )
 * releases previous reservation
 * only true, if there was something to release
 */
+bool schiene_t::unreserve(vehicle_t* v)
+{
+	convoi_t* c = v ? v->get_convoi() : nullptr;
+	return unreserve(c ? c->self : convoihandle_t());
+}
+
+
 bool schiene_t::unreserve(convoihandle_t c)
 {
-	// is this tile reserved by us?
-	if(reserved.is_bound()  &&  reserved==c) {
-		reserved = convoihandle_t();
+	if(reserved.is_bound()  &&  reserved == c) {
+		// promote co-reservation to primary slot (if any)
+		reserved      = reserved2;
+		reserved_dir  = reserved2_dir;
+		reserved2     = convoihandle_t();
+		reserved2_dir = ribi_t::none;
+		if(schiene_t::show_reservations) {
+			set_flag( obj_t::dirty );
+		}
+		return true;
+	}
+	if(reserved2.is_bound()  &&  reserved2 == c) {
+		reserved2     = convoihandle_t();
+		reserved2_dir = ribi_t::none;
 		if(schiene_t::show_reservations) {
 			set_flag( obj_t::dirty );
 		}
diff --git a/src/simutrans/obj/way/schiene.h b/src/simutrans/obj/way/schiene.h
index 814d0fcf1..4ae9783fd 100644
--- a/src/simutrans/obj/way/schiene.h
+++ b/src/simutrans/obj/way/schiene.h
@@ -9,6 +9,7 @@
 
 #include "weg.h"
 #include "../../convoihandle.h"
+#include "../../dataobj/ribi.h"
 
 class vehicle_t;
 
@@ -20,10 +21,15 @@ class vehicle_t;
 class schiene_t : public weg_t
 {
 protected:
-	/**
-	* Bound when this block was successfully reserved by the convoi
-	*/
 	convoihandle_t reserved;
+	ribi_t::ribi   reserved_dir  = ribi_t::none;
+	convoihandle_t reserved2;
+	ribi_t::ribi   reserved2_dir = ribi_t::none;
+
+	// Two corner_sets that share no ribi bits don't cross visually (NW+SE or NE+SW).
+	static bool can_co_reserve_dirs(ribi_t::ribi d1, ribi_t::ribi d2) {
+		return ribi_t::is_bend(d1) && ribi_t::is_bend(d2) && (d1 & d2) == 0;
+	}
 
 public:
 	static const way_desc_t *default_schiene;
@@ -47,7 +53,7 @@ public:
 	/**
 	* true, if this rail can be reserved
 	*/
-	bool can_reserve(convoihandle_t c) const { return !reserved.is_bound()  ||  c==reserved; }
+	bool can_reserve(convoihandle_t c) const { return !reserved.is_bound()  ||  c==reserved  ||  c==reserved2; }
 
 	/**
 	* true, if this rail can be reserved
@@ -65,9 +71,10 @@ public:
 	virtual bool unreserve( convoihandle_t c);
 
 	/**
-	* releases previous reservation
+	* releases previous reservation — derives convoy handle from the vehicle
+	* so that co-reserved convoys are correctly identified.
 	*/
-	bool unreserve( vehicle_t *) { return unreserve(reserved); }
+	bool unreserve( vehicle_t *v);
 
 	/* called before deletion;
 	 * last chance to unreserve tiles ...
@@ -75,10 +82,18 @@ public:
 	void cleanup(player_t *player) OVERRIDE;
 
 	/**
-	* gets the related convoi
+	* gets the related convoi (primary slot)
 	*/
 	convoihandle_t get_reserved_convoi() const {return reserved;}
 
+	/**
+	* true if convoy c holds either the primary or the secondary reservation.
+	*/
+	bool is_reserved_by(convoihandle_t c) const {
+		return (reserved.is_bound()  &&  reserved  == c)
+		    || (reserved2.is_bound() &&  reserved2 == c);
+	}
+
 	void rdwr(loadsave_t *file) OVERRIDE;
 
 	/**
diff --git a/src/simutrans/simconvoi.cc b/src/simutrans/simconvoi.cc
index 89231e685..21ff05ecf 100644
--- a/src/simutrans/simconvoi.cc
+++ b/src/simutrans/simconvoi.cc
@@ -285,7 +285,10 @@ void convoi_t::reserve_route()
 		for(  route_t::index_t idx = back()->get_route_index();  idx < next_reservation_index  /*&&  idx < route.get_count()*/;  idx++  ) {
 			if(  grund_t *gr = welt->lookup( route.at(idx) )  ) {
 				if(  schiene_t *sch = (schiene_t *)gr->get_weg( front()->get_waytype() )  ) {
-					sch->reserve( self, ribi_type( route.at(max(1u,idx)-1u), route.at(min(route.get_count()-1u,idx+1u)) ) );
+					const koord3d prev = route.at(max(1u,idx)-1u);
+					const koord3d curr = route.at(idx);
+					const koord3d next = route.at(min(route.get_count()-1u,idx+1u));
+					sch->reserve( self, ribi_t::backward(ribi_type(prev,curr)) | ribi_type(curr,next) );
 				}
 			}
 		}
diff --git a/src/simutrans/vehicle/rail_vehicle.cc b/src/simutrans/vehicle/rail_vehicle.cc
index 5c6d148b6..53c266a20 100644
--- a/src/simutrans/vehicle/rail_vehicle.cc
+++ b/src/simutrans/vehicle/rail_vehicle.cc
@@ -778,15 +778,22 @@ bool rail_vehicle_t::block_reserver(const route_t *route, route_t::index_t start
 					next_signal_index = i;
 				}
 			}
-			if (!sch1->reserve(cnv->self, ribi_type(route->at(max(1u,i)-1u), route->at(min(route->get_count()-1u, i+1u))))) {
-				success = false;
-			}
-			if (gr->has_two_ways()) {
-				// we may need to reserve the other way as well
-				if (schiene_t* sch0 = dynamic_cast<schiene_t*>(gr->get_weg_nr(gr->get_weg_nr(0) == sch1))) {
-					// the other way is reservable too => try to reserve it
-					if (!sch0->reserve(cnv->self, ribi_type(route->at(max(1u,i)-1u), route->at(min(route->get_count()-1u, i+1u))))) {
-						success = false;
+			{
+				// corner_set = entry_border | exit_border; correctly identifies which corner of the
+				// tile a turning train occupies, enabling safe co-reservation of opposite corners.
+				const ribi_t::ribi corner_set =
+					ribi_t::backward(ribi_type(route->at(max(1u,i)-1u), pos))
+					| ribi_type(pos, route->at(min(route->get_count()-1u,i+1u)));
+				if (!sch1->reserve(cnv->self, corner_set)) {
+					success = false;
+				}
+				if (gr->has_two_ways()) {
+					// we may need to reserve the other way as well
+					if (schiene_t* sch0 = dynamic_cast<schiene_t*>(gr->get_weg_nr(gr->get_weg_nr(0) == sch1))) {
+						// the other way is reservable too => try to reserve it
+						if (!sch0->reserve(cnv->self, corner_set)) {
+							success = false;
+						}
 					}
 				}
 			}
@@ -865,7 +872,8 @@ void rail_vehicle_t::leave_tile()
 	if(last) {
 		if(grund_t* gr = welt->lookup(get_pos())) {
 			if(schiene_t* sch0 = (schiene_t*)gr->get_weg(get_waytype())) {
-				sch0->unreserve(this);
+				const convoihandle_t self_cnv = cnv ? cnv->self : convoihandle_t();
+				sch0->unreserve(self_cnv);
 				// tell next signal?
 				// and switch to red
 				if(sch0->has_signal()) {
@@ -877,7 +885,7 @@ void rail_vehicle_t::leave_tile()
 					// we may need to reserve the other way as well
 					if (schiene_t* sch1 = dynamic_cast<schiene_t*>(gr->get_weg_nr(gr->get_weg_nr(0) == sch0))) {
 						// the other way is reservable too => unreserve it
-						sch1->unreserve(this);
+						sch1->unreserve(self_cnv);
 					}
 				}
 			}
