diff --git a/src/aircraft_cmd.cpp b/src/aircraft_cmd.cpp
index 78597e3a30..82d9af94b4 100644
--- a/src/aircraft_cmd.cpp
+++ b/src/aircraft_cmd.cpp
@@ -175,7 +175,7 @@ void Aircraft::GetImage(Direction direction, EngineImageType image_type, Vehicle
 {
 	uint8_t spritenum = this->spritenum;
 
-	if (is_custom_sprite(spritenum)) {
+	if (IsCustomVehicleSpriteNum(spritenum)) {
 		GetCustomVehicleSprite(this, direction, image_type, result);
 		if (result->IsValid()) return;
 
@@ -191,7 +191,7 @@ void GetRotorImage(const Aircraft *v, EngineImageType image_type, VehicleSpriteS
 	assert(v->subtype == AIR_HELICOPTER);
 
 	const Aircraft *w = v->Next()->Next();
-	if (is_custom_sprite(v->spritenum)) {
+	if (IsCustomVehicleSpriteNum(v->spritenum)) {
 		GetCustomRotorSprite(v, image_type, result);
 		if (result->IsValid()) return;
 	}
@@ -205,7 +205,7 @@ static void GetAircraftIcon(EngineID engine, EngineImageType image_type, Vehicle
 	const Engine *e = Engine::Get(engine);
 	uint8_t spritenum = e->u.air.image_index;
 
-	if (is_custom_sprite(spritenum)) {
+	if (IsCustomVehicleSpriteNum(spritenum)) {
 		GetCustomVehicleIcon(engine, DIR_W, image_type, result);
 		if (result->IsValid()) return;
 
diff --git a/src/depot_gui.cpp b/src/depot_gui.cpp
index cd2b28111a..1aabc6bcc2 100644
--- a/src/depot_gui.cpp
+++ b/src/depot_gui.cpp
@@ -228,7 +228,7 @@ void InitDepotWindowBlockSizes()
 		if (!e->IsEnabled()) continue;
 
 		uint w = TRAININFO_DEFAULT_VEHICLE_WIDTH;
-		if (e->GetGRF() != nullptr && is_custom_sprite(e->u.rail.image_index)) {
+		if (e->GetGRF() != nullptr && IsCustomVehicleSpriteNum(e->u.rail.image_index)) {
 			w = e->GetGRF()->traininfo_vehicle_width;
 			if (w != VEHICLEINFO_FULL_VEHICLE_WIDTH) {
 				/* Hopeless.
diff --git a/src/newgrf/newgrf_act0_aircraft.cpp b/src/newgrf/newgrf_act0_aircraft.cpp
index 1c614d0d32..d246544784 100644
--- a/src/newgrf/newgrf_act0_aircraft.cpp
+++ b/src/newgrf/newgrf_act0_aircraft.cpp
@@ -43,9 +43,9 @@ static ChangeInfoResult AircraftVehicleChangeInfo(uint first, uint last, int pro
 				uint8_t orig_spriteid = spriteid;
 
 				/* aircraft have different custom id in the GRF file */
-				if (spriteid == 0xFF) spriteid = 0xFD;
+				if (spriteid == 0xFF) spriteid = CUSTOM_VEHICLE_SPRITENUM;
 
-				if (spriteid < 0xFD) spriteid >>= 1;
+				if (spriteid < CUSTOM_VEHICLE_SPRITENUM) spriteid >>= 1;
 
 				if (IsValidNewGRFImageIndex<VEH_AIRCRAFT>(spriteid)) {
 					avi->image_index = spriteid;
diff --git a/src/newgrf/newgrf_act0_roadvehs.cpp b/src/newgrf/newgrf_act0_roadvehs.cpp
index aba759f8f7..e8cf67f6bb 100644
--- a/src/newgrf/newgrf_act0_roadvehs.cpp
+++ b/src/newgrf/newgrf_act0_roadvehs.cpp
@@ -62,9 +62,9 @@ static ChangeInfoResult RoadVehicleChangeInfo(uint first, uint last, int prop, B
 				uint8_t orig_spriteid = spriteid;
 
 				/* cars have different custom id in the GRF file */
-				if (spriteid == 0xFF) spriteid = 0xFD;
+				if (spriteid == 0xFF) spriteid = CUSTOM_VEHICLE_SPRITENUM;
 
-				if (spriteid < 0xFD) spriteid >>= 1;
+				if (spriteid < CUSTOM_VEHICLE_SPRITENUM) spriteid >>= 1;
 
 				if (IsValidNewGRFImageIndex<VEH_ROAD>(spriteid)) {
 					rvi->image_index = spriteid;
diff --git a/src/newgrf/newgrf_act0_ships.cpp b/src/newgrf/newgrf_act0_ships.cpp
index f24be94bda..0e1bfe0b17 100644
--- a/src/newgrf/newgrf_act0_ships.cpp
+++ b/src/newgrf/newgrf_act0_ships.cpp
@@ -45,9 +45,9 @@ static ChangeInfoResult ShipVehicleChangeInfo(uint first, uint last, int prop, B
 				uint8_t orig_spriteid = spriteid;
 
 				/* ships have different custom id in the GRF file */
-				if (spriteid == 0xFF) spriteid = 0xFD;
+				if (spriteid == 0xFF) spriteid = CUSTOM_VEHICLE_SPRITENUM;
 
-				if (spriteid < 0xFD) spriteid >>= 1;
+				if (spriteid < CUSTOM_VEHICLE_SPRITENUM) spriteid >>= 1;
 
 				if (IsValidNewGRFImageIndex<VEH_SHIP>(spriteid)) {
 					svi->image_index = spriteid;
diff --git a/src/newgrf/newgrf_act0_trains.cpp b/src/newgrf/newgrf_act0_trains.cpp
index 29fa3e473a..445dbf294b 100644
--- a/src/newgrf/newgrf_act0_trains.cpp
+++ b/src/newgrf/newgrf_act0_trains.cpp
@@ -98,7 +98,7 @@ ChangeInfoResult RailVehicleChangeInfo(uint first, uint last, int prop, ByteRead
 
 				/* TTD sprite IDs point to a location in a 16bit array, but we use it
 				 * as an array index, so we need it to be half the original value. */
-				if (spriteid < 0xFD) spriteid >>= 1;
+				if (spriteid < CUSTOM_VEHICLE_SPRITENUM) spriteid >>= 1;
 
 				if (IsValidNewGRFImageIndex<VEH_TRAIN>(spriteid)) {
 					rvi->image_index = spriteid;
diff --git a/src/newgrf/newgrf_internal_vehicle.h b/src/newgrf/newgrf_internal_vehicle.h
index 53526e7526..2e1abf3b70 100644
--- a/src/newgrf/newgrf_internal_vehicle.h
+++ b/src/newgrf/newgrf_internal_vehicle.h
@@ -58,12 +58,12 @@ void ConvertTTDBasePrice(uint32_t base_pointer, const char *error_location, Pric
  * Helper to check whether an image index is valid for a particular NewGRF vehicle.
  * @tparam T The type of vehicle.
  * @param image_index The image index to check.
- * @return True iff the image index is valid, or 0xFD (use new graphics).
+ * @return True iff the image index is valid, or CUSTOM_VEHICLE_SPRITENUM (use new graphics).
  */
 template <VehicleType T>
 static inline bool IsValidNewGRFImageIndex(uint8_t image_index)
 {
-	 return image_index == 0xFD || IsValidImageIndex<T>(image_index);
+	return image_index == CUSTOM_VEHICLE_SPRITENUM || IsValidImageIndex<T>(image_index);
 }
 
 ChangeInfoResult CommonVehicleChangeInfo(EngineInfo *ei, int prop, ByteReader &buf);
diff --git a/src/newgrf_engine.cpp b/src/newgrf_engine.cpp
index 88dfd74b33..62158dca6e 100644
--- a/src/newgrf_engine.cpp
+++ b/src/newgrf_engine.cpp
@@ -838,8 +838,8 @@ static uint32_t VehicleGetVariable(Vehicle *v, const VehicleScopeResolver *objec
 		case 0x46: return v->GetEngine()->grf_prop.local_id;
 		case 0x47: return GB(v->GetEngine()->grf_prop.local_id, 8, 8);
 		case 0x48:
-			if (v->type != VEH_TRAIN || v->spritenum != 0xFD) return v->spritenum;
-			return HasBit(Train::From(v)->flags, VRF_REVERSE_DIRECTION) ? 0xFE : 0xFD;
+			if (v->type != VEH_TRAIN || v->spritenum != CUSTOM_VEHICLE_SPRITENUM) return v->spritenum;
+			return HasBit(Train::From(v)->flags, VRF_REVERSE_DIRECTION) ? CUSTOM_VEHICLE_SPRITENUM_REVERSED : CUSTOM_VEHICLE_SPRITENUM;
 
 		case 0x49: return v->day_counter;
 		case 0x4A: return v->breakdowns_since_last_service;
diff --git a/src/roadveh_cmd.cpp b/src/roadveh_cmd.cpp
index b56e8b772d..71649eaa5c 100644
--- a/src/roadveh_cmd.cpp
+++ b/src/roadveh_cmd.cpp
@@ -107,7 +107,7 @@ static void GetRoadVehIcon(EngineID engine, EngineImageType image_type, VehicleS
 	const Engine *e = Engine::Get(engine);
 	uint8_t spritenum = e->u.road.image_index;
 
-	if (is_custom_sprite(spritenum)) {
+	if (IsCustomVehicleSpriteNum(spritenum)) {
 		GetCustomVehicleIcon(engine, DIR_W, image_type, result);
 		if (result->IsValid()) return;
 
@@ -122,8 +122,9 @@ void RoadVehicle::GetImage(Direction direction, EngineImageType image_type, Vehi
 {
 	uint8_t spritenum = this->spritenum;
 
-	if (is_custom_sprite(spritenum)) {
-		GetCustomVehicleSprite(this, (Direction)(direction + 4 * IS_CUSTOM_SECONDHEAD_SPRITE(spritenum)), image_type, result);
+	if (IsCustomVehicleSpriteNum(spritenum)) {
+		if (spritenum == CUSTOM_VEHICLE_SPRITENUM_REVERSED) direction = ReverseDir(direction);
+		GetCustomVehicleSprite(this, direction, image_type, result);
 		if (result->IsValid()) return;
 
 		spritenum = this->GetEngine()->original_image_index;
diff --git a/src/ship_cmd.cpp b/src/ship_cmd.cpp
index badc781ab0..2ed052c76f 100644
--- a/src/ship_cmd.cpp
+++ b/src/ship_cmd.cpp
@@ -83,7 +83,7 @@ static void GetShipIcon(EngineID engine, EngineImageType image_type, VehicleSpri
 	const Engine *e = Engine::Get(engine);
 	uint8_t spritenum = e->u.ship.image_index;
 
-	if (is_custom_sprite(spritenum)) {
+	if (IsCustomVehicleSpriteNum(spritenum)) {
 		GetCustomVehicleIcon(engine, DIR_W, image_type, result);
 		if (result->IsValid()) return;
 
@@ -137,7 +137,7 @@ void Ship::GetImage(Direction direction, EngineImageType image_type, VehicleSpri
 
 	if (image_type == EIT_ON_MAP) direction = this->rotation;
 
-	if (is_custom_sprite(spritenum)) {
+	if (IsCustomVehicleSpriteNum(spritenum)) {
 		GetCustomVehicleSprite(this, direction, image_type, result);
 		if (result->IsValid()) return;
 
diff --git a/src/train_cmd.cpp b/src/train_cmd.cpp
index b7b10f543e..3cc2755b69 100644
--- a/src/train_cmd.cpp
+++ b/src/train_cmd.cpp
@@ -441,7 +441,7 @@ int Train::GetCursorImageOffset() const
 		int reference_width = TRAININFO_DEFAULT_VEHICLE_WIDTH;
 
 		const Engine *e = this->GetEngine();
-		if (e->GetGRF() != nullptr && is_custom_sprite(e->u.rail.image_index)) {
+		if (e->GetGRF() != nullptr && IsCustomVehicleSpriteNum(e->u.rail.image_index)) {
 			reference_width = e->GetGRF()->traininfo_vehicle_width;
 		}
 
@@ -461,7 +461,7 @@ int Train::GetDisplayImageWidth(Point *offset) const
 	int vehicle_pitch = 0;
 
 	const Engine *e = this->GetEngine();
-	if (e->GetGRF() != nullptr && is_custom_sprite(e->u.rail.image_index)) {
+	if (e->GetGRF() != nullptr && IsCustomVehicleSpriteNum(e->u.rail.image_index)) {
 		reference_width = e->GetGRF()->traininfo_vehicle_width;
 		vehicle_pitch = e->GetGRF()->traininfo_vehicle_pitch;
 	}
@@ -495,8 +495,9 @@ void Train::GetImage(Direction direction, EngineImageType image_type, VehicleSpr
 
 	if (HasBit(this->flags, VRF_REVERSE_DIRECTION)) direction = ReverseDir(direction);
 
-	if (is_custom_sprite(spritenum)) {
-		GetCustomVehicleSprite(this, (Direction)(direction + 4 * IS_CUSTOM_SECONDHEAD_SPRITE(spritenum)), image_type, result);
+	if (IsCustomVehicleSpriteNum(spritenum)) {
+		if (spritenum == CUSTOM_VEHICLE_SPRITENUM_REVERSED) direction = ReverseDir(direction);
+		GetCustomVehicleSprite(this, direction, image_type, result);
 		if (result->IsValid()) return;
 
 		spritenum = this->GetEngine()->original_image_index;
@@ -516,7 +517,7 @@ static void GetRailIcon(EngineID engine, bool rear_head, int &y, EngineImageType
 	Direction dir = rear_head ? DIR_E : DIR_W;
 	uint8_t spritenum = e->u.rail.image_index;
 
-	if (is_custom_sprite(spritenum)) {
+	if (IsCustomVehicleSpriteNum(spritenum)) {
 		GetCustomVehicleIcon(engine, dir, image_type, result);
 		if (result->IsValid()) {
 			if (e->GetGRF() != nullptr) {
diff --git a/src/vehicle_func.h b/src/vehicle_func.h
index e5207e969e..33701feae2 100644
--- a/src/vehicle_func.h
+++ b/src/vehicle_func.h
@@ -21,9 +21,18 @@
 #include "track_type.h"
 #include "livery.h"
 
-#define is_custom_sprite(x) (x >= 0xFD)
-#define IS_CUSTOM_FIRSTHEAD_SPRITE(x) (x == 0xFD)
-#define IS_CUSTOM_SECONDHEAD_SPRITE(x) (x == 0xFE)
+/**
+ * Special values for Vehicle::spritenum and (Aircraft|Rail|Road|Ship)VehicleInfo::image_index
+ */
+enum CustomVehicleSpriteNum {
+	CUSTOM_VEHICLE_SPRITENUM = 0xFD, ///< Vehicle sprite from NewGRF
+	CUSTOM_VEHICLE_SPRITENUM_REVERSED = 0xFE, ///< Vehicle sprite from NewGRF with reverse driving direction (from articulation callback)
+};
+
+static inline bool IsCustomVehicleSpriteNum(uint8_t spritenum)
+{
+	return spritenum >= CUSTOM_VEHICLE_SPRITENUM;
+}
 
 static const TimerGameEconomy::Date VEHICLE_PROFIT_MIN_AGE{CalendarTime::DAYS_IN_YEAR * 2}; ///< Only vehicles older than this have a meaningful profit.
 static const Money VEHICLE_PROFIT_THRESHOLD = 10000;        ///< Threshold for a vehicle to be considered making good profit.