diff --git a/src/simutrans/builder/hausbauer.cc b/src/simutrans/builder/hausbauer.cc
index 1bc93c894..e9246c856 100644
--- a/src/simutrans/builder/hausbauer.cc
+++ b/src/simutrans/builder/hausbauer.cc
@@ -142,6 +142,11 @@ bool hausbauer_t::successfully_loaded()
 	for(auto const& i : desc_table) {
 		building_desc_t const* const desc = i.value;
 
+		// How tall this building is drawn. Only now: it needs the base tile
+		// raster width, which is set when the "Outside" ground is registered,
+		// and nothing guarantees that happened before this building was read.
+		const_cast<building_desc_t *>(desc)->calc_height_clearance();
+
 		if (desc->is_city_building()) {
 			// find city build sizes
 			if (desc->get_size().x > 3 || desc->get_size().y > 3) {
@@ -886,7 +891,7 @@ const building_desc_t* hausbauer_t::get_special(uint32 bev, building_desc_t::bty
  * @param start_level the minimum level of the house/station
  * @param cl allowed climates
  */
-static const building_desc_t* get_city_building_from_list(const vector_tpl<const building_desc_t*>& list, int start_level, uint16 time, climate cl, uint32 clusters, sint16 minsize, sint16 maxsize, vector_tpl<const building_desc_t*>* exclude )
+static const building_desc_t* get_city_building_from_list(const vector_tpl<const building_desc_t*>& list, int start_level, uint16 time, climate cl, uint32 clusters, sint16 minsize, sint16 maxsize, vector_tpl<const building_desc_t*>* exclude, bool no_upper_storey )
 {
 	weighted_vector_tpl<const building_desc_t *> selections(16);
 	int level = start_level;
@@ -910,7 +915,11 @@ static const building_desc_t* get_city_building_from_list(const vector_tpl<const
 		     desc->is_available(time)  &&
 		     // size check
 			(desc->get_area()>=minsize  &&  desc->get_area() <= maxsize)  &&
-			(!exclude  ||  !exclude->is_contained(desc))
+			(!exclude  ||  !exclude->is_contained(desc))  &&
+			// an elevated way overhead: the replacement must fit under it, by
+			// the same test that stopped the way being built over a tall
+			// building in the first place (way_builder_t::is_allowed_step)
+			(!no_upper_storey  ||  !desc->has_upper_storey())
 		) {
 			desc_at_least = desc;
 			if( thislevel == level ) {
@@ -946,21 +955,21 @@ static const building_desc_t* get_city_building_from_list(const vector_tpl<const
 }
 
 
-const building_desc_t* hausbauer_t::get_commercial(int level, uint16 time, climate cl, uint32 clusters, sint16 minsize, sint16 maxsize, vector_tpl<const building_desc_t*>* exclude)
+const building_desc_t* hausbauer_t::get_commercial(int level, uint16 time, climate cl, uint32 clusters, sint16 minsize, sint16 maxsize, vector_tpl<const building_desc_t*>* exclude, bool no_upper_storey)
 {
-	return get_city_building_from_list(city_commercial, level, time, cl, clusters, minsize, maxsize, exclude );
+	return get_city_building_from_list(city_commercial, level, time, cl, clusters, minsize, maxsize, exclude, no_upper_storey );
 }
 
 
-const building_desc_t* hausbauer_t::get_industrial(int level, uint16 time, climate cl, uint32 clusters, sint16 minsize, sint16 maxsize, vector_tpl<const building_desc_t*>* exclude)
+const building_desc_t* hausbauer_t::get_industrial(int level, uint16 time, climate cl, uint32 clusters, sint16 minsize, sint16 maxsize, vector_tpl<const building_desc_t*>* exclude, bool no_upper_storey)
 {
-	return get_city_building_from_list(city_industry, level, time, cl, clusters, minsize, maxsize, exclude );
+	return get_city_building_from_list(city_industry, level, time, cl, clusters, minsize, maxsize, exclude, no_upper_storey );
 }
 
 
-const building_desc_t* hausbauer_t::get_residential(int level, uint16 time, climate cl, uint32 clusters, sint16 minsize, sint16 maxsize, vector_tpl<const building_desc_t*> *exclude)
+const building_desc_t* hausbauer_t::get_residential(int level, uint16 time, climate cl, uint32 clusters, sint16 minsize, sint16 maxsize, vector_tpl<const building_desc_t*> *exclude, bool no_upper_storey)
 {
-	return get_city_building_from_list(city_residential, level, time, cl, clusters, minsize, maxsize, exclude );
+	return get_city_building_from_list(city_residential, level, time, cl, clusters, minsize, maxsize, exclude, no_upper_storey );
 }
 
 
diff --git a/src/simutrans/builder/hausbauer.h b/src/simutrans/builder/hausbauer.h
index e2bba9177..43a887544 100644
--- a/src/simutrans/builder/hausbauer.h
+++ b/src/simutrans/builder/hausbauer.h
@@ -85,13 +85,13 @@ public:
 	static void fill_menu(tool_selector_t* tool_selector, building_desc_t::btype, waytype_t wt, sint16 sound_ok);
 
 	/// @returns a random commercial building matching the requirements.
-	static const building_desc_t* get_commercial(int level, uint16 time, climate c, uint32 clusters, sint16 minsize, sint16 maxsize, vector_tpl<const building_desc_t*>* exclude = NULL);
+	static const building_desc_t* get_commercial(int level, uint16 time, climate c, uint32 clusters, sint16 minsize, sint16 maxsize, vector_tpl<const building_desc_t*>* exclude = NULL, bool no_upper_storey = false);
 
 	/// @returns a random industrial building matching the requirements.
-	static const building_desc_t* get_industrial(int level, uint16 time, climate cl, uint32 clusters, sint16 minsize, sint16 maxsize, vector_tpl<const building_desc_t*>* exclude = NULL);
+	static const building_desc_t* get_industrial(int level, uint16 time, climate cl, uint32 clusters, sint16 minsize, sint16 maxsize, vector_tpl<const building_desc_t*>* exclude = NULL, bool no_upper_storey = false);
 
 	/// @returns a random residential building matching the requirements.
-	static const building_desc_t* get_residential(int level, uint16 time, climate cl, uint32 clusters, sint16 minsize, sint16 maxsize, vector_tpl<const building_desc_t*>* exclude = NULL);
+	static const building_desc_t* get_residential(int level, uint16 time, climate cl, uint32 clusters, sint16 minsize, sint16 maxsize, vector_tpl<const building_desc_t*>* exclude = NULL, bool no_upper_storey = false);
 
 	/// @returns headquarters with level @p level (takes the first matching one)
 	static const building_desc_t* get_headquarters(int level, uint16 time);
diff --git a/src/simutrans/builder/wegbauer.cc b/src/simutrans/builder/wegbauer.cc
index ad08374b8..ae45d7ae1 100644
--- a/src/simutrans/builder/wegbauer.cc
+++ b/src/simutrans/builder/wegbauer.cc
@@ -606,7 +606,7 @@ 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()->get_background(0,1,0) != IMG_EMPTY) {
+				if(!check_owner(gb->get_owner(),player_builder)  ||  gb->get_tile()->has_upper_storey()) {
 					return false;
 				}
 				// building above houses is expensive ... avoid it!
diff --git a/src/simutrans/descriptor/building_desc.cc b/src/simutrans/descriptor/building_desc.cc
index 164311c1e..d21fed770 100644
--- a/src/simutrans/descriptor/building_desc.cc
+++ b/src/simutrans/descriptor/building_desc.cc
@@ -11,9 +11,77 @@
 #include "building_desc.h"
 #include "intro_dates.h"
 #include "../network/checksum.h"
+#include "../display/simgraph.h"
+#include "../dataobj/environment.h"
+#include "../simconst.h"
 
 
 
+/**
+ * How far the artwork rises above the tile's ground line, in height levels.
+ *
+ * The ground line is at half the cell and image coordinates run from the top
+ * left, so one row of artwork reaches (cell/2 - y) above it. Each further image
+ * row is drawn a whole tile higher again (gebaeude_t::display).
+ *
+ * Read from the base images and returned as a count of levels, so the value does
+ * not change with the zoom.
+ */
+void building_tile_desc_t::calc_height_levels()
+{
+	height_levels = 0;
+
+	const sint16 cell = gfx->get_base_tile_raster_width();
+	// image pixels per height level. Not the raw tile_height from simuconf.tab:
+	// pak128 says 8 and pak128.german says 16, and both are 128 wide.
+	const sint16 level_h = max((sint16)1, (sint16)tile_raster_scale_y(TILE_HEIGHT_STEP, cell));
+
+	for(  uint8 season = 0;  season < seasons;  season++  ) {
+		image_array_t const* const imglist = get_child<image_array_t>(0 + 2 * season);
+		if(  imglist == NULL  ) {
+			continue;
+		}
+
+		// topmost row holding an image, and how far the ground row reaches up
+		sint16 rows  = 0;
+		sint16 above = 0;
+		for(  uint16 h = 0;  h < imglist->get_count();  h++  ) {
+			if(  image_t const* const image = imglist->get_image(h, 0)  ) {
+				rows = h + 1;
+				if(  h == 0  ) {
+					above = cell / 2 - image->y;
+				}
+			}
+		}
+		if(  rows == 0  ) {
+			continue;
+		}
+
+		// artwork that stays below the ground line does not rise above it at all
+		const sint16 levels = ((rows - 1) * cell + max((sint16)0, above)) / level_h;
+
+		if(  levels > height_levels  ) {
+			height_levels = (uint8)min(levels, (sint16)255);
+		}
+	}
+}
+
+
+void building_desc_t::calc_height_clearance()
+{
+	height_clearance = 0;
+
+	for(  uint16 i = 0;  i < (uint16)(layouts * size.x * size.y);  i++  ) {
+		// the tiles are ours; they are only const through get_tile()
+		building_tile_desc_t *tile = const_cast<building_tile_desc_t *>(get_tile(i));
+		tile->calc_height_levels();
+		if(  tile->get_height_levels() > height_clearance  ) {
+			height_clearance = tile->get_height_levels();
+		}
+	}
+}
+
+
 /**
  * Calculate which layout the tile belongs to from the index.
  */
diff --git a/src/simutrans/descriptor/building_desc.h b/src/simutrans/descriptor/building_desc.h
index 9130a875d..964e6c616 100644
--- a/src/simutrans/descriptor/building_desc.h
+++ b/src/simutrans/descriptor/building_desc.h
@@ -37,6 +37,22 @@ class building_tile_desc_t : public obj_desc_t {
 	uint8  phases;  ///< number of animation phases
 	uint16 index;
 
+	/**
+	 * How far this tile's artwork rises above the tile's ground line, in HEIGHT
+	 * LEVELS. Derived from the images by calc_height_levels(), because it cannot
+	 * be read off the image list directly:
+	 *
+	 * - the height index of get_background() is not a height level. Each further
+	 *   image row lifts the artwork by a whole tile (gebaeude_t::display does
+	 *   ypos -= raster_width), which is eight levels in pak128;
+	 * - within a row, the artwork's own top edge matters, because makeobj crops
+	 *   every image to its opaque bounding box and stores where that box was
+	 *   (image_writer.cc). A single-row building can still be several levels tall.
+	 *
+	 * Held in levels, not pixels, so that it does not depend on the zoom.
+	 */
+	uint8 height_levels;
+
 public:
 	void set_desc(const building_desc_t *building_desc) { building = building_desc; }
 
@@ -46,10 +62,30 @@ public:
 	uint8 get_seasons() const { return seasons; }
 	uint8 get_phases() const { return phases; }
 
+	/// @see height_levels
+	uint8 get_height_levels() const { return height_levels; }
+
+	/**
+	 * Derive height_levels from the images. Must run once the pakset is fully
+	 * loaded: it needs the base tile raster width, which is set only when the
+	 * "Outside" ground is registered, and that ordering is not guaranteed while
+	 * objects are still being read.
+	 */
+	void calc_height_levels();
+
 	bool has_image() const {
 		return get_background(0,0,0)!=IMG_EMPTY  ||  get_foreground(0,0)!=IMG_EMPTY;
 	}
 
+	/**
+	 * Does this tile draw anything above the ground storey?
+	 * This is the test that decides whether an elevated way may pass over the
+	 * building, so it lives here rather than being spelled out at each use.
+	 */
+	bool has_upper_storey() const {
+		return get_background(0, 1, 0) != IMG_EMPTY;
+	}
+
 	image_id get_background(uint16 phase, uint16 height, uint8 season) const
 	{
 		image_array_t const* const imglist = get_child<image_array_t>(0 + 2 * season);
@@ -209,6 +245,9 @@ private:
 
 	uint16 preservation_year_month;
 
+	/// @see get_height_clearance
+	uint8 height_clearance;
+
 	bool is_type(building_desc_t::btype u) const {
 		return type == u;
 	}
@@ -285,8 +324,38 @@ public:
 		return get_child<building_tile_desc_t>(index + 2);
 	}
 
+	/**
+	 * The clearance this building needs above its ground, in height levels: the
+	 * tallest of its tiles, over every layout.
+	 *
+	 * It is the whole building and not the single tile on purpose. A tile that
+	 * draws nothing above the ground may still belong to a tower, and asking
+	 * only the tile a way happens to cross lets the way through the low corner
+	 * of a tall building. In pak128.german, 90 of the 128 multi-tile city
+	 * buildings have a one-row tile beside a taller one.
+	 */
+	uint8 get_height_clearance() const { return height_clearance; }
+
+	/// Derive get_height_clearance() from the tiles. @see building_tile_desc_t::calc_height_levels
+	void calc_height_clearance();
+
 	const building_tile_desc_t *get_tile(uint8 layout, sint16 x, sint16 y) const;
 
+	/**
+	 * Does any tile of this building draw above the ground storey?
+	 * An elevated way cannot pass over such a building (see way_builder_t),
+	 * so a replacement chosen for a spot that already has one overhead must
+	 * not have an upper storey either.
+	 */
+	bool has_upper_storey() const {
+		for(  uint16 i = 0;  i < layouts * size.x * size.y;  i++  ) {
+			if(  get_tile(i)->has_upper_storey()  ) {
+				return true;
+			}
+		}
+		return false;
+	}
+
 	// returns true,if building can be rotated
 	bool can_rotate() const {
 		if(size.x!=size.y  &&  layouts==1) {
diff --git a/src/simutrans/world/simcity.cc b/src/simutrans/world/simcity.cc
index 14eb83e9f..5c881234e 100644
--- a/src/simutrans/world/simcity.cc
+++ b/src/simutrans/world/simcity.cc
@@ -3505,11 +3505,32 @@ bool stadt_t::renovate_city_building(gebaeude_t *gb)
 	building_desc_t::btype want_to_have = building_desc_t::unknown;
 	int sum = 0;
 
+	// An elevated way over this tile is only there because the building below
+	// was short enough to allow it (way_builder_t::is_allowed_step). Renovating
+	// into something taller would produce a building the player could never
+	// have built the way over, so the replacement has to pass the same test.
+	// If nothing does, no renovation happens and the block simply stops growing
+	// upwards, which is the intended ceiling rather than a failure.
+	//
+	// monorailboden only, which is the elevated-way ground - any waytype, so
+	// elevated rail, monorail, maglev and tram are all covered. NOT
+	// brueckenboden: the bridge builder never tested building height, so tall
+	// buildings under a bridge have always been legal and the argument above
+	// does not apply to them. Changing that would be a separate decision.
+	bool no_upper_storey = false;
+	if(  const grund_t *gr_here = welt->lookup_kartenboden(k)  ) {
+		const grund_t *above = welt->lookup(gr_here->get_pos()
+			+ koord3d(0, 0, welt->get_settings().get_way_height_clearance()));
+		no_upper_storey = above
+			&&  above->get_typ() == grund_t::monorailboden
+			&&  above->get_weg_nr(0) != NULL;
+	}
+
 	// try to build
 	const building_desc_t* h = NULL;
 	if (sum_commercial > sum_industrial && sum_commercial > sum_residential) {
 		// we must check, if we can really update to higher level ...
-		h = hausbauer_t::get_commercial(level+1, current_month, cl, neighbor_building_clusters, 1, max_area, &exclude_desc);
+		h = hausbauer_t::get_commercial(level+1, current_month, cl, neighbor_building_clusters, 1, max_area, &exclude_desc, no_upper_storey);
 		if(  h != NULL  &&  h->get_level() >= level+1  ) {
 			want_to_have = building_desc_t::city_com;
 			sum = sum_commercial;
@@ -3519,7 +3540,7 @@ bool stadt_t::renovate_city_building(gebaeude_t *gb)
 	if(    (sum_industrial > sum_commercial  &&  sum_industrial > sum_residential) ||
 	       (sum_commercial > sum_residential  &&  want_to_have == building_desc_t::unknown)  ) {
 		// we must check, if we can really update to higher level ...
-		h = hausbauer_t::get_industrial(level+1 , current_month, cl, neighbor_building_clusters, 1, max_area, &exclude_desc);
+		h = hausbauer_t::get_industrial(level+1, current_month, cl, neighbor_building_clusters, 1, max_area, &exclude_desc, no_upper_storey);
 		if(  h != NULL  &&  h->get_level() >= level+1  ) {
 			want_to_have = building_desc_t::city_ind;
 			sum = sum_industrial;
@@ -3529,7 +3550,7 @@ bool stadt_t::renovate_city_building(gebaeude_t *gb)
 	// (sum_wohnung>sum_industrie  &&  sum_wohnung>sum_gewerbe
 	if(  want_to_have == building_desc_t::unknown  ) {
 		// we must check, if we can really update to higher level ...
-		h = hausbauer_t::get_residential(level+1, current_month, cl, neighbor_building_clusters, 1, max_area, &exclude_desc);
+		h = hausbauer_t::get_residential(level+1, current_month, cl, neighbor_building_clusters, 1, max_area, &exclude_desc, no_upper_storey);
 		if(  h != NULL  &&  h->get_level() >= level+1  ) {
 			want_to_have = building_desc_t::city_res;
 			sum = sum_residential;
diff --git a/src/simutrans/world/simplan.cc b/src/simutrans/world/simplan.cc
index 01d5b8e0a..f0144276b 100644
--- a/src/simutrans/world/simplan.cc
+++ b/src/simutrans/world/simplan.cc
@@ -484,17 +484,32 @@ void planquadrat_t::display_obj(const sint16 xpos, const sint16 ypos, const sint
 		// clip everything at the next tile above
 		if(  i < ground_size  ) {
 			// there are grounds above => check height of height object below
+			// How far the objects on this tile rise above its ground, in HEIGHT
+			// LEVELS - which is the unit (htop - hmin) is measured in below.
 			sint16 max_height = 0;
-			sint16 rw = gfx->get_base_tile_raster_width();
-			const sint16 clip_h = rw / 2 + (env_t::pak_height_conversion_factor * rw / 8);
+			// get_image_offset() returns the zoomed offsets, so take the raster
+			// width zoomed too; the quotient is the same count either way.
+			const sint16 rw = gfx->get_tile_raster_width();
+			const sint16 level_h = max((sint16)1, (sint16)tile_raster_scale_y(TILE_HEIGHT_STEP, rw));
 
 			for (uint8 i = 0; i < gr0->obj_count(); i++) {
 				obj_t* o = gr0->obj_bei(i);
-				if (o->get_typ() == obj_t::baum   ||  dynamic_cast<gebaeude_t *>(o)) {
+				if (const gebaeude_t *gb = dynamic_cast<gebaeude_t *>(o)) {
+					// a building's get_image() is its ground row alone, so
+					// measuring that image calls a tower one storey tall. The
+					// descriptor was told the whole story at load time.
+					max_height = max(max_height, (sint16)gb->get_tile()->get_height_levels());
+				}
+				else if (o->get_typ() == obj_t::baum) {
 					image_id img = o->get_image();
 					if (img != IMG_EMPTY) {
 						const scr_rect area = gfx->get_image_offset(img);
-						max_height = max(max_height, (area.h - area.y) / clip_h);
+						// The ground line is at half the cell and image
+						// coordinates run from the top left, so the artwork
+						// reaches (rw/2 - y) above it. What stood here,
+						// (area.h - area.y), subtracted an offset from a size:
+						// negative for 144 of pak128's 550 tree images.
+						max_height = max(max_height, max((sint16)0, (sint16)(rw / 2 - area.y)) / level_h);
 					}
 				}
 			}
