diff --git a/src/simutrans/builder/wegbauer.cc b/src/simutrans/builder/wegbauer.cc
index 091832473..7e13c2b38 100644
--- a/src/simutrans/builder/wegbauer.cc
+++ b/src/simutrans/builder/wegbauer.cc
@@ -2668,7 +2668,11 @@ bool way_builder_t::build_tunnel_tile()
 			grund_t* gr = welt->lookup(i);
 			if (weg_t* weg = gr->get_weg(desc->get_wtyp())) {
 				ribi_t::ribi r = weg->get_ribi_unmasked();
-				if (ribi_t::is_bend(r) || ribi_t::all == r) {
+				if (weg->has_sign() || weg->has_signal()) {
+					// Recalculate signs after the way's diagonal state changes.
+					gr->calc_image();
+				}
+				else if (ribi_t::is_bend(r) || ribi_t::all == r) {
 					weg->calc_image();
 				}
 			}
@@ -2705,7 +2709,11 @@ void way_builder_t::build_elevated()
 			grund_t* gr = welt->lookup(i);
 			if (weg_t* weg = gr->get_weg(desc->get_wtyp())) {
 				ribi_t::ribi r = weg->get_ribi_unmasked();
-				if (ribi_t::is_bend(r) || ribi_t::all == r) {
+				if (weg->has_sign() || weg->has_signal()) {
+					// Recalculate signs after the way's diagonal state changes.
+					gr->calc_image();
+				}
+				else if (ribi_t::is_bend(r) || ribi_t::all == r) {
 					weg->calc_image();
 				}
 			}
@@ -2807,7 +2815,11 @@ void way_builder_t::build_road()
 			grund_t* gr = welt->lookup(route[i]);
 			if (weg_t* weg = gr->get_weg(desc->get_wtyp())) {
 				ribi_t::ribi r = weg->get_ribi_unmasked();
-				if (ribi_t::is_bend(r) || ribi_t::all == r) {
+				if (weg->has_sign() || weg->has_signal()) {
+					// Recalculate signs after the way's diagonal state changes.
+					gr->calc_image();
+				}
+				else if (ribi_t::is_bend(r) || ribi_t::all == r) {
 					weg->calc_image();
 				}
 			}
@@ -2937,7 +2949,11 @@ void way_builder_t::build_track()
 			grund_t* gr = welt->lookup(route[i]);
 			if (weg_t* weg = gr->get_weg(desc->get_wtyp())) {
 				ribi_t::ribi r = weg->get_ribi_unmasked();
-				if (ribi_t::is_bend(r) || ribi_t::all == r) {
+				if (weg->has_sign() || weg->has_signal()) {
+					// Recalculate signs after the way's diagonal state changes.
+					gr->calc_image();
+				}
+				else if (ribi_t::is_bend(r) || ribi_t::all == r) {
 					weg->calc_image();
 				}
 			}
diff --git a/src/simutrans/descriptor/reader/roadsign_reader.cc b/src/simutrans/descriptor/reader/roadsign_reader.cc
index c0933cea3..25c593a91 100644
--- a/src/simutrans/descriptor/reader/roadsign_reader.cc
+++ b/src/simutrans/descriptor/reader/roadsign_reader.cc
@@ -23,6 +23,31 @@ void roadsign_reader_t::register_obj(obj_desc_t *&data)
 {
 	roadsign_desc_t *desc = static_cast<roadsign_desc_t *>(data);
 
+	// Whether a usable diagonal image list is present is derived here, not stored in
+	// the binary (as wayobj does). makeobj already rejects a partial list at compile
+	// time; the count check below only defends against hand-crafted paks.
+	desc->has_diagonal_imgs = false;
+	if(  desc->has_diagonal_slot  ) {
+		const image_list_t *diag = desc->get_diagonal_list();
+		const uint16 diag_count = diag->get_count();
+		if(  diag_count == 0  ) {
+			// no diagonal graphics defined: use the normal images (silent)
+		}
+		else if(  diag_count != desc->get_count()  ) {
+			dbg->warning("roadsign_reader_t::register_obj()",
+				"%s: diagonal image count (%i) does not match the normal count (%i); diagonal images ignored",
+				desc->get_name(), diag_count, desc->get_count());
+		}
+		else if(  diag->is_empty()  ) {
+			// parallel count but every slot is the "-" placeholder: no diagonal graphics
+			dbg->message("roadsign_reader_t::register_obj()",
+				"%s: diagonal image list contains no usable images; using normal images", desc->get_name());
+		}
+		else {
+			desc->has_diagonal_imgs = true;
+		}
+	}
+
 	roadsign_t::register_desc(desc);
 
 	checksum_t *chk = new checksum_t();
@@ -46,7 +71,21 @@ obj_desc_t *roadsign_reader_t::read_node(FILE *fp, obj_node_info_t &node)
 	const int version = v & 0x8000 ? v & 0x7FFF : 0;
 	roadsign_desc_t *desc = new roadsign_desc_t();
 
-	if (version == 6) {
+	if (version == 7) {
+		// like version 6, plus an optional diagonal image list stored as child 3
+		// (which shifts the cursor/icon skin to child 4). The header layout is
+		// identical to version 6; only the child structure changed.
+		desc->min_speed   = kmh_to_speed(decode_uint16(p));
+		desc->price       = decode_sint64(p);
+		desc->maintenance = decode_sint64(p);
+		desc->flags       = decode_uint16(p);
+		desc->offset_left = decode_sint8(p);
+		desc->wtyp        = decode_uint8(p);
+		desc->intro_date  = decode_uint16(p);
+		desc->retire_date = decode_uint16(p);
+		desc->has_diagonal_slot = true;
+	}
+	else if (version == 6) {
 		// cost as sint64, maintenance added
 		desc->min_speed   = kmh_to_speed(decode_uint16(p));
 		desc->price       = decode_sint64(p);
diff --git a/src/simutrans/descriptor/roadsign_desc.h b/src/simutrans/descriptor/roadsign_desc.h
index f20c9ebf0..897deb117 100644
--- a/src/simutrans/descriptor/roadsign_desc.h
+++ b/src/simutrans/descriptor/roadsign_desc.h
@@ -18,10 +18,23 @@
 /**
  * Road signs
  *
- * Child nodes:
+ * Child nodes (descriptor version <= 6):
  *  0   Name
  *  1   Copyright
  *  2   Image list
+ *  3   Cursor/icon skin (optional)
+ *
+ * Child nodes (descriptor version 7):
+ *  0   Name
+ *  1   Copyright
+ *  2   Image list (normal, straight/flat ways)
+ *  3   Image list (optional diagonal variant, empty when unused)
+ *  4   Cursor/icon skin (always present)
+ *
+ * The diagonal list is parallel to the normal list (same directions, states and
+ * electrified variants) and is used only on diagonal ways. It is purely visual;
+ * when it is absent, or an individual slot is empty, the normal image is used.
+ * See roadsign_writer.cc for the .dat syntax.
  */
 class roadsign_desc_t : public obj_desc_transport_infrastructure_t {
 	friend class roadsign_reader_t;
@@ -33,6 +46,17 @@ private:
 
 	uint16 min_speed; // 0 = no min speed
 
+	bool has_diagonal_slot = false;  // v7 pak: child 3 is the diagonal list, cursor is child 4
+	bool has_diagonal_imgs = false;  // a usable diagonal list is present (derived at load)
+
+	image_id get_list_image_id(uint8 child, ribi_t::dir dir) const
+	{
+		image_t const* const image = get_child<image_list_t>(child)->get_image(dir);
+		return image != NULL ? image->get_id() : IMG_EMPTY;
+	}
+
+	image_list_t const* get_diagonal_list() const { return get_child<image_list_t>(3); }
+
 public:
 	enum types {
 		NONE                  = 0,
@@ -47,15 +71,29 @@ public:
 		SIGN_PRIORITY_SIGNAL  = 1U << 8
 	};
 
-	image_id get_image_id(ribi_t::dir dir) const
+	image_id get_image_id(ribi_t::dir dir) const { return get_list_image_id(2, dir); }
+
+	// Diagonal image for this index, with per-slot fallback to the normal image
+	// (an undefined "-" slot never renders blank).
+	image_id get_diagonal_image_id(ribi_t::dir dir) const
 	{
-		image_t const* const image = get_child<image_list_t>(2)->get_image(dir);
-		return image != NULL ? image->get_id() : IMG_EMPTY;
+		if(  has_diagonal_imgs  ) {
+			const image_id diag = get_list_image_id(3, dir);
+			if(  diag != IMG_EMPTY  ) {
+				return diag;
+			}
+		}
+		return get_list_image_id(2, dir);
 	}
 
+	// convenience: pick the diagonal or the normal list without duplicating index math
+	image_id get_image_id(ribi_t::dir dir, bool diagonal) const { return diagonal ? get_diagonal_image_id(dir) : get_image_id(dir); }
+
+	bool has_diagonal_images() const { return has_diagonal_imgs; }
+
 	uint16 get_count() const { return get_child<image_list_t>(2)->get_count(); }
 
-	skin_desc_t const* get_cursor() const { return get_child<skin_desc_t>(3); }
+	skin_desc_t const* get_cursor() const { return get_child<skin_desc_t>(has_diagonal_slot ? 4 : 3); }
 
 	uint16 get_min_speed() const { return min_speed; }
 
diff --git a/src/simutrans/descriptor/writer/roadsign_writer.cc b/src/simutrans/descriptor/writer/roadsign_writer.cc
index 55e5502c4..c97b0649f 100644
--- a/src/simutrans/descriptor/writer/roadsign_writer.cc
+++ b/src/simutrans/descriptor/writer/roadsign_writer.cc
@@ -19,6 +19,14 @@ static const char* private_sign_directions[] = {"ns", "ew"};
 static const char* traffic_light_directions[] = {"n", "s", "w", "e", "nw", "se", "sw", "ne"};
 static const char* general_sign_directions[] = {"n", "s", "w", "e"};
 
+/*
+ * Optional diagonal images (descriptor version 7): "diagonalimage[dir][state]",
+ * dir = n/s/w/e, used only on diagonal ways. The block must be parallel to the
+ * normal image[dir][state] block - same directions, states and electrified
+ * variants, i.e. the same image count. A single "-" slot falls back to the
+ * matching normal image. Traffic lights and private ways are excluded.
+ */
+
 // parse "image[direction][state]" syntax
 void parse_images_2d(slist_tpl<std::string>& keys, tabfileobj_t& obj, roadsign_desc_t::types flags)
 {
@@ -59,6 +67,32 @@ void parse_images_2d(slist_tpl<std::string>& keys, tabfileobj_t& obj, roadsign_d
 }
 
 
+// parse the optional "diagonalimage[direction][state]" syntax (n/s/w/e).
+// Leaves keys empty when no diagonal images are defined.
+void parse_diagonal_images(slist_tpl<std::string>& keys, tabfileobj_t& obj)
+{
+	const char** directions = general_sign_directions;
+	const uint8 dir_cnt = lengthof(general_sign_directions);
+
+	for(  uint8 state=0;  state<8;  state++  ) {
+		for(  uint8 idx = 0;  idx < dir_cnt;  idx++  ) {
+			char buf[64];
+			sprintf(buf, "diagonalimage[%s][%i]", directions[idx], state);
+			const char* img = obj.get(buf);
+			if(  !*img  ) {
+				if(  idx==0  ) {
+					// no (further) diagonal state defined => stop here
+					return;
+				}
+				// a direction in the middle of a state is missing => fatal error
+				dbg->fatal("roadsign_writer", "%s is missing!", buf);
+			}
+			keys.append(img);
+		}
+	}
+}
+
+
 // parse "image[number]" syntax
 void parse_images_numbered(slist_tpl<std::string>& keys, tabfileobj_t& obj)
 {
@@ -115,7 +149,7 @@ void roadsign_writer_t::write_obj(FILE* fp, obj_node_t& parent, tabfileobj_t& ob
 
 	obj_node_t node(this, 28, &parent);
 
-	node.write_version(fp, 6);
+	node.write_version(fp, 7);
 	node.write_uint16(fp, min_speed);
 	node.write_sint64(fp, price);
 	node.write_sint64(fp, maintenance);
@@ -148,15 +182,28 @@ void roadsign_writer_t::write_obj(FILE* fp, obj_node_t& parent, tabfileobj_t& ob
 	}
 	imagelist_writer_t::instance()->write_obj(fp, node, keys);
 
-	// probably add some icons, if defined
-	slist_tpl<std::string> cursorkeys;
-
-	const char *c = obj.get("cursor"), *i=obj.get("icon");
-	cursorkeys.append(c);
-	cursorkeys.append(i);
-	if (*c || *i) {
-		cursorskin_writer_t::instance()->write_obj(fp, node, obj, cursorkeys);
+	// child 3: optional diagonal image list (v7). Always written so the child layout
+	// stays fixed; empty for traffic lights, private ways and the numbered syntax.
+	slist_tpl<std::string> diagonal_keys;
+	if(  !(flags & roadsign_desc_t::PRIVATE_ROAD)  &&  !*obj.get("image[ne][0]")  &&  !*obj.get("image[0]")  ) {
+		parse_diagonal_images(diagonal_keys, obj);
 	}
+	// The diagonal list must be parallel to the normal list. Zero images is fine;
+	// any other count is a partial list and a pak error - reject it here rather than
+	// at load. ("-" slots count and fall back per-slot at draw time.)
+	if(  !diagonal_keys.empty()  &&  diagonal_keys.get_count() != keys.get_count()  ) {
+		dbg->fatal("roadsign_writer",
+			"diagonal image count (%u) does not match the normal image count (%u); "
+			"provide a full parallel diagonalimage[dir][state] block or none",
+			diagonal_keys.get_count(), keys.get_count());
+	}
+	imagelist_writer_t::instance()->write_obj(fp, node, diagonal_keys);
+
+	// child 4: cursor/icon skin. Always written since v7 so its child index is fixed.
+	slist_tpl<std::string> cursorkeys;
+	cursorkeys.append(obj.get("cursor"));
+	cursorkeys.append(obj.get("icon"));
+	cursorskin_writer_t::instance()->write_obj(fp, node, obj, cursorkeys);
 
 	node.check_and_write_header(fp);
 }
diff --git a/src/simutrans/obj/roadsign.cc b/src/simutrans/obj/roadsign.cc
index 161a3e43a..13d0fe4eb 100644
--- a/src/simutrans/obj/roadsign.cc
+++ b/src/simutrans/obj/roadsign.cc
@@ -303,6 +303,10 @@ void roadsign_t::calc_image()
 		foreground_image = IMG_EMPTY;
 		ribi_t::ribi temp_dir = dir;
 
+		// diagonal graphics only when the static sign sits on a diagonal way
+		const weg_t *way = gr->get_weg(desc->get_wtyp()!=tram_wt ? desc->get_wtyp() : track_wt);
+		const bool use_diagonal = desc->has_diagonal_images()  &&  way  &&  way->is_diagonal();
+
 		if(  gr->get_typ()==grund_t::tunnelboden  &&  gr->ist_karten_boden()  &&
 			(grund_t::underground_mode==grund_t::ugm_none  ||  (grund_t::underground_mode==grund_t::ugm_level  &&  gr->get_hoehe()<grund_t::underground_level))   ) {
 			// entering tunnel here: hide the image further in if not undergroud/sliced
@@ -321,38 +325,38 @@ void roadsign_t::calc_image()
 			const sint16 YOFF = desc->get_offset_left();
 
 			if(temp_dir&ribi_t::east) {
-				tmp_image = desc->get_image_id(3);
+				tmp_image = desc->get_image_id(3, use_diagonal);
 				xoff += XOFF;
 				yoff += -YOFF;
 			}
 
 			if(temp_dir&ribi_t::north) {
 				if(tmp_image!=IMG_EMPTY) {
-					foreground_image = desc->get_image_id(0);
+					foreground_image = desc->get_image_id(0, use_diagonal);
 					after_xoffset += -XOFF;
 					after_yoffset += -YOFF;
 				}
 				else {
-					tmp_image = desc->get_image_id(0);
+					tmp_image = desc->get_image_id(0, use_diagonal);
 					xoff += -XOFF;
 					yoff += -YOFF;
 				}
 			}
 
 			if(temp_dir&ribi_t::west) {
-				foreground_image = desc->get_image_id(2);
+				foreground_image = desc->get_image_id(2, use_diagonal);
 				after_xoffset += -XOFF;
 				after_yoffset += YOFF;
 			}
 
 			if(temp_dir&ribi_t::south) {
 				if(foreground_image!=IMG_EMPTY) {
-					tmp_image = desc->get_image_id(1);
+					tmp_image = desc->get_image_id(1, use_diagonal);
 					xoff += XOFF;
 					yoff += YOFF;
 				}
 				else {
-					foreground_image = desc->get_image_id(1);
+					foreground_image = desc->get_image_id(1, use_diagonal);
 					after_xoffset += XOFF;
 					after_yoffset += YOFF;
 				}
@@ -361,28 +365,28 @@ void roadsign_t::calc_image()
 		else {
 
 			if(temp_dir&ribi_t::east) {
-				foreground_image = desc->get_image_id(3);
+				foreground_image = desc->get_image_id(3, use_diagonal);
 			}
 
 			if(temp_dir&ribi_t::north) {
 				if(foreground_image!=IMG_EMPTY) {
-					tmp_image = desc->get_image_id(0);
+					tmp_image = desc->get_image_id(0, use_diagonal);
 				}
 				else {
-					foreground_image = desc->get_image_id(0);
+					foreground_image = desc->get_image_id(0, use_diagonal);
 				}
 			}
 
 			if(temp_dir&ribi_t::west) {
-				tmp_image = desc->get_image_id(2);
+				tmp_image = desc->get_image_id(2, use_diagonal);
 			}
 
 			if(temp_dir&ribi_t::south) {
 				if(tmp_image!=IMG_EMPTY) {
-					foreground_image = desc->get_image_id(1);
+					foreground_image = desc->get_image_id(1, use_diagonal);
 				}
 				else {
-					tmp_image = desc->get_image_id(1);
+					tmp_image = desc->get_image_id(1, use_diagonal);
 				}
 			}
 		}
diff --git a/src/simutrans/obj/signal.cc b/src/simutrans/obj/signal.cc
index 776d41559..517871bd3 100644
--- a/src/simutrans/obj/signal.cc
+++ b/src/simutrans/obj/signal.cc
@@ -70,6 +70,9 @@ void signal_t::calc_image()
 				offset = (desc->is_pre_signal()  ||  desc->is_priority_signal()) ? 12 : 8;
 			}
 
+			// diagonal graphics only on diagonal track (same indices as normal)
+			const bool use_diagonal = desc->has_diagonal_images()  &&  sch->is_diagonal();
+
 			// vertical offset of the signal positions
 			if(full_hang==slope_t::flat) {
 				yoff = -gr->get_weg_yoff();
@@ -108,38 +111,38 @@ void signal_t::calc_image()
 				const sint16 YOFF = desc->get_offset_left();
 
 				if(temp_dir&ribi_t::east) {
-					image = desc->get_image_id(3+state*4+offset);
+					image = desc->get_image_id(3+state*4+offset, use_diagonal);
 					xoff += XOFF;
 					yoff += -YOFF;
 				}
 
 				if(temp_dir&ribi_t::north) {
 					if(image!=IMG_EMPTY) {
-						foreground_image = desc->get_image_id(0+state*4+offset);
+						foreground_image = desc->get_image_id(0+state*4+offset, use_diagonal);
 						after_xoffset += -XOFF;
 						after_yoffset += -YOFF;
 					}
 					else {
-						image = desc->get_image_id(0+state*4+offset);
+						image = desc->get_image_id(0+state*4+offset, use_diagonal);
 						xoff += -XOFF;
 						yoff += -YOFF;
 					}
 				}
 
 				if(temp_dir&ribi_t::west) {
-					foreground_image = desc->get_image_id(2+state*4+offset);
+					foreground_image = desc->get_image_id(2+state*4+offset, use_diagonal);
 					after_xoffset += -XOFF;
 					after_yoffset += YOFF;
 				}
 
 				if(temp_dir&ribi_t::south) {
 					if(foreground_image!=IMG_EMPTY) {
-						image = desc->get_image_id(1+state*4+offset);
+						image = desc->get_image_id(1+state*4+offset, use_diagonal);
 						xoff += XOFF;
 						yoff += YOFF;
 					}
 					else {
-						foreground_image = desc->get_image_id(1+state*4+offset);
+						foreground_image = desc->get_image_id(1+state*4+offset, use_diagonal);
 						after_xoffset += XOFF;
 						after_yoffset += YOFF;
 					}
@@ -147,28 +150,28 @@ void signal_t::calc_image()
 			}
 			else {
 				if(temp_dir&ribi_t::east) {
-					foreground_image = desc->get_image_id(3+state*4+offset);
+					foreground_image = desc->get_image_id(3+state*4+offset, use_diagonal);
 				}
 
 				if(temp_dir&ribi_t::north) {
 					if(foreground_image==IMG_EMPTY) {
-						foreground_image = desc->get_image_id(0+state*4+offset);
+						foreground_image = desc->get_image_id(0+state*4+offset, use_diagonal);
 					}
 					else {
-						image = desc->get_image_id(0+state*4+offset);
+						image = desc->get_image_id(0+state*4+offset, use_diagonal);
 					}
 				}
 
 				if(temp_dir&ribi_t::west) {
-					image = desc->get_image_id(2+state*4+offset);
+					image = desc->get_image_id(2+state*4+offset, use_diagonal);
 				}
 
 				if(temp_dir&ribi_t::south) {
 					if(image==IMG_EMPTY) {
-						image = desc->get_image_id(1+state*4+offset);
+						image = desc->get_image_id(1+state*4+offset, use_diagonal);
 					}
 					else {
-						foreground_image = desc->get_image_id(1+state*4+offset);
+						foreground_image = desc->get_image_id(1+state*4+offset, use_diagonal);
 					}
 				}
 			}
