diff --git a/src/simutrans/builder/brueckenbauer.cc b/src/simutrans/builder/brueckenbauer.cc
index 82df8f9b0..bd4a823c7 100644
--- a/src/simutrans/builder/brueckenbauer.cc
+++ b/src/simutrans/builder/brueckenbauer.cc
@@ -11,6 +11,7 @@
 #include "../dataobj/scenario.h"
 
 #include "../descriptor/bridge_desc.h"
+#include "../descriptor/building_desc.h"
 
 #include "../ground/boden.h"
 #include "../ground/brueckenboden.h"
@@ -20,6 +21,7 @@
 
 #include "../obj/bruecke.h"
 #include "../obj/depot.h"
+#include "../obj/gebaeude.h"
 #include "../obj/leitung2.h"
 #include "../obj/pillar.h"
 #include "../obj/signal.h"
@@ -513,6 +515,21 @@ const char *bridge_builder_t::can_span_bridge(const player_t* player, koord3d st
 			return "bridge is too high for its type!";
 		}
 
+		// every building below must fit under the deck ("" lets a higher deck be tried)
+		for (uint8 b = 0; b < pl->get_boden_count(); b++) {
+			grund_t* building_ground = pl->get_boden_bei(b);
+			if (building_ground->get_hoehe() >= height) {
+				continue;
+			}
+			const gebaeude_t* gb = building_ground->find<gebaeude_t>();
+			if (gb == NULL) {
+				gb = building_ground->get_depot();
+			}
+			if (gb  &&  height - building_ground->get_hoehe() < gb->get_tile()->get_desc()->get_height_clearance()) {
+				return "";
+			}
+		}
+
 		if (gr->hat_weg(air_wt)  &&  gr->get_styp(air_wt) == type_runway) {
 			return "No bridges over runways!";
 		}
diff --git a/src/simutrans/builder/wegbauer.cc b/src/simutrans/builder/wegbauer.cc
index aef1b28a1..1d58cb3ae 100644
--- a/src/simutrans/builder/wegbauer.cc
+++ b/src/simutrans/builder/wegbauer.cc
@@ -627,8 +627,8 @@ bool way_builder_t::is_allowed_step(const grund_t *from, const grund_t *to, sint
 			}
 			if(gb) {
 				// no halt => citybuilding => do not touch
-				// also check for too high buildings ...
-				if(!check_owner(gb->get_owner(),player_builder)  ||  gb->get_tile()->has_upper_storey()) {
+				// also the whole building must fit below the deck (not just the crossed tile)
+				if(!check_owner(gb->get_owner(),player_builder)  ||  gb->get_tile()->get_desc()->get_height_clearance() > get_way_height_offset(to)) {
 					return false;
 				}
 				// building above houses is expensive ... avoid it!
diff --git a/tests/all_tests.nut b/tests/all_tests.nut
index 545840cbf..e04c01aeb 100644
--- a/tests/all_tests.nut
+++ b/tests/all_tests.nut
@@ -29,6 +29,7 @@ include("tests/test_terraform")
 include("tests/test_transport")
 include("tests/test_trees")
 include("tests/test_way_bridge")
+include("tests/test_way_elevated")
 include("tests/test_way_road")
 include("tests/test_way_runway")
 include("tests/test_way_tram")
@@ -202,6 +203,9 @@ all_tests <- [
 	test_way_bridge_build_above_way,
 	test_way_bridge_build_above_runway,
 	test_way_bridge_planner,
+	test_way_bridge_over_building,
+	test_way_bridge_over_fitting_stacked_building,
+	test_way_elevated_over_building,
 	test_way_road_build_single_tile,
 	test_way_road_build_straight,
 	test_way_road_build_bend,
diff --git a/tests/tests/test_way_bridge.nut b/tests/tests/test_way_bridge.nut
index 234990f6c..2de83be83 100644
--- a/tests/tests/test_way_bridge.nut
+++ b/tests/tests/test_way_bridge.nut
@@ -509,6 +509,87 @@ function test_way_bridge_build_above_way()
 }
 
 
+function test_way_bridge_over_building()
+{
+	local remover = command_x(tool_remove_way)
+	local setslope = command_x.set_slope
+	local pl = player_x(0)
+	local public_pl = player_x(1)
+	local bridge_desc = bridge_desc_x.get_available_bridges(wt_road)[0]
+	ASSERT_TRUE(bridge_desc != null)
+
+	// same geometry as test_way_bridge_build_above_way (deck one level up over x=3),
+	// but with a tall building on the valley floor instead of a road: the bridge must
+	// be refused because the building needs more clearance than the deck leaves.
+	ASSERT_EQUAL(command_x(tool_add_city).work(public_pl, coord3d(12, 12, 0), "0"), null)
+
+	ASSERT_EQUAL(setslope(pl, coord3d(3, 2, 0), slope.south), null)
+	ASSERT_EQUAL(setslope(pl, coord3d(3, 5, 0), slope.north), null)
+
+	// control: over an empty valley the same bridge builds fine
+	ASSERT_EQUAL(command_x.build_bridge(pl, coord3d(3, 2, 0), coord3d(3, 5, 0), bridge_desc), null)
+	ASSERT_EQUAL(remover.work(pl, coord3d(3, 2, 0), coord3d(3, 5, 0), "" + wt_road), null)
+
+	// a tall building (Theatre) on the valley floor now makes the same bridge refuse
+	ASSERT_EQUAL(command_x(tool_build_house).work(public_pl, coord3d(3, 3, 0), "1#Theatre"), null)
+	ASSERT_EQUAL(command_x.build_bridge(pl, coord3d(3, 2, 0), coord3d(3, 5, 0), bridge_desc), "")
+	// no bridge deck must have been left over the building at any height
+	ASSERT_TRUE(square_x(3, 3).get_tile_at_height(1) == null)
+	ASSERT_TRUE(square_x(3, 4).get_tile_at_height(1) == null)
+	ASSERT_TRUE(square_x(3, 3).get_tile_at_height(2) == null)
+	ASSERT_TRUE(square_x(3, 4).get_tile_at_height(2) == null)
+
+	ASSERT_EQUAL(command_x(tool_remover).work(public_pl, coord3d(3, 3, 0)), null)
+
+	ASSERT_EQUAL(setslope(pl, coord3d(3, 2, 0), slope.all_down_slope), null)
+	ASSERT_EQUAL(setslope(pl, coord3d(3, 5, 0), slope.all_down_slope), null)
+	ASSERT_EQUAL(command_x(tool_remover).work(public_pl, coord3d(12, 12, 0)), null)
+	ASSERT_EQUAL(remover.work(public_pl, coord3d(11, 13, 0), coord3d(13, 13, 0), "" + wt_road), null)
+
+	RESET_ALL_PLAYER_FUNDS()
+}
+
+
+function test_way_bridge_over_fitting_stacked_building()
+{
+	local remover = command_x(tool_remove_way)
+	local setslope = command_x.set_slope
+	local pl = player_x(0)
+	local bridge_desc = bridge_desc_x.get_available_bridges(wt_road)[0]
+	local station_desc = building_desc_x.get_available_stations(building_desc_x.station, wt_road, good_desc_x.passenger)[0]
+	ASSERT_TRUE(bridge_desc != null)
+	ASSERT_TRUE(station_desc != null)
+
+	// lower bridge, east-west, with its deck at z=1
+	ASSERT_EQUAL(setslope(pl, coord3d(4, 9, 0), slope.east), null)
+	ASSERT_EQUAL(setslope(pl, coord3d(7, 9, 0), slope.west), null)
+	ASSERT_EQUAL(command_x.build_bridge(pl, coord3d(4, 9, 0), coord3d(7, 9, 0), bridge_desc), null)
+
+	// raised endpoints let a north-south bridge try z=1, then z=2
+	ASSERT_EQUAL(setslope(pl, coord3d(5, 7, 0), slope.all_up_slope), null)
+	ASSERT_EQUAL(setslope(pl, coord3d(5, 11, 0), slope.all_up_slope), null)
+
+	// control: the upper bridge builds over the empty lower deck
+	ASSERT_EQUAL(command_x.build_bridge(pl, coord3d(5, 7, 1), coord3d(5, 11, 1), bridge_desc), null)
+	ASSERT_EQUAL(remover.work(pl, coord3d(5, 7, 1), coord3d(5, 11, 1), "" + wt_road), null)
+
+	// pak64's station fits exactly in the one-level gap and must not be overblocked
+	ASSERT_EQUAL(command_x.build_station(pl, coord3d(5, 9, 1), station_desc), null)
+	ASSERT_EQUAL(command_x.build_bridge(pl, coord3d(5, 7, 1), coord3d(5, 11, 1), bridge_desc), null)
+	ASSERT_TRUE(square_x(5, 9).get_tile_at_height(2) != null)
+
+	ASSERT_EQUAL(remover.work(pl, coord3d(5, 7, 1), coord3d(5, 11, 1), "" + wt_road), null)
+	ASSERT_EQUAL(command_x(tool_remover).work(pl, coord3d(5, 9, 1)), null)
+	ASSERT_EQUAL(remover.work(pl, coord3d(4, 9, 0), coord3d(7, 9, 0), "" + wt_road), null)
+	ASSERT_EQUAL(setslope(pl, coord3d(4, 9, 0), slope.all_down_slope), null)
+	ASSERT_EQUAL(setslope(pl, coord3d(7, 9, 0), slope.all_down_slope), null)
+	ASSERT_EQUAL(setslope(pl, coord3d(5, 7, 1), slope.all_down_slope), null)
+	ASSERT_EQUAL(setslope(pl, coord3d(5, 11, 1), slope.all_down_slope), null)
+
+	RESET_ALL_PLAYER_FUNDS()
+}
+
+
 function test_way_bridge_build_above_runway()
 {
 	local pl = player_x(0)
diff --git a/tests/tests/test_way_elevated.nut b/tests/tests/test_way_elevated.nut
new file mode 100644
index 000000000..b131eb391
--- /dev/null
+++ b/tests/tests/test_way_elevated.nut
@@ -0,0 +1,43 @@
+//
+// This file is part of the Simutrans project under the Artistic License.
+// (see LICENSE.txt)
+//
+
+// elevated ways must not be buildable at insufficient height over an existing building
+// (pak64 assets: Theatre = 2x2, two levels tall; Tennis_Court = 2x2, flat)
+
+function test_way_elevated_over_building()
+{
+	local public_pl = player_x(1)
+
+	local elevated = way_desc_x.get_available_ways(wt_monorail, st_elevated)[0]
+	ASSERT_TRUE(elevated != null)
+
+	ASSERT_EQUAL(command_x(tool_add_city).work(public_pl, coord3d(8, 8, 0), "0"), null)
+
+	// (A) control: over empty ground the same two-tile span is allowed
+	ASSERT_EQUAL(command_x.build_way(public_pl, coord3d(3, 4, 0), coord3d(4, 4, 0), elevated, true), null)
+	ASSERT_EQUAL(command_x(tool_remove_way).work(public_pl, coord3d(3, 4, 1), coord3d(4, 4, 1), "" + wt_monorail), null)
+
+	// (B) a building drawn taller than the way clearance must refuse the way over it.
+	//     Theatre draws two levels although no tile carries a second image row, so the
+	//     old has_upper_storey() test wrongly let a way through it.
+	ASSERT_EQUAL(command_x(tool_build_house).work(public_pl, coord3d(3, 4, 0), "1#Theatre"), null)
+	ASSERT_EQUAL(command_x.build_way(public_pl, coord3d(3, 4, 0), coord3d(4, 4, 0), elevated, true), "")
+	// nothing must have been built above the building
+	ASSERT_TRUE(square_x(3, 4).get_tile_at_height(1) == null)
+	ASSERT_TRUE(square_x(4, 4).get_tile_at_height(1) == null)
+	ASSERT_EQUAL(command_x(tool_remover).work(public_pl, coord3d(3, 4, 0)), null) // removes the whole 2x2 building
+
+	// (C) a building that does fit below the deck must still be allowed. Tennis_Court is a
+	//     2x2 that draws nothing above its ground (clearance 0), so the way passes over it.
+	ASSERT_EQUAL(command_x(tool_build_house).work(public_pl, coord3d(3, 4, 0), "1#Tennis_Court"), null)
+	ASSERT_EQUAL(command_x.build_way(public_pl, coord3d(3, 4, 0), coord3d(4, 4, 0), elevated, true), null)
+	ASSERT_EQUAL(command_x(tool_remove_way).work(public_pl, coord3d(3, 4, 1), coord3d(4, 4, 1), "" + wt_monorail), null)
+	ASSERT_EQUAL(command_x(tool_remover).work(public_pl, coord3d(3, 4, 0)), null)
+
+	ASSERT_EQUAL(command_x(tool_remover).work(public_pl, coord3d(8, 8, 0)), null) // remove city
+	ASSERT_EQUAL(command_x(tool_remove_way).work(public_pl, coord3d(7, 9, 0), coord3d(9, 9, 0), "" + wt_road), null)
+
+	RESET_ALL_PLAYER_FUNDS()
+}
