diff --git a/tests/all_tests.nut b/tests/all_tests.nut
index e116a0cb9..147a41a63 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")
@@ -207,6 +208,32 @@ all_tests <- [
 	test_way_bridge_planner_flat_ends,
 	test_way_bridge_planner_ownership,
 	test_way_bridge_planner_forbidden_by_scenario,
+	test_way_elevated_build_straight_we,
+	test_way_elevated_build_straight_ew,
+	test_way_elevated_build_straight_ns,
+	test_way_elevated_build_bend,
+	test_way_elevated_build_single_tile_refused,
+	test_way_elevated_over_existing_road,
+	test_way_elevated_crosses_road,
+	test_way_elevated_crosses_elevated,
+	test_way_elevated_over_water,
+	test_way_elevated_start_on_water,
+	test_way_elevated_over_single_slope_refused,
+	test_way_elevated_over_double_slope,
+	test_way_elevated_start_on_slope,
+	test_way_elevated_at_map_edge,
+	test_way_elevated_ignores_funds,
+	test_way_elevated_foreign_owner,
+	test_way_elevated_rebuild_and_extend,
+	test_way_elevated_start_from_upper_level_refused,
+	test_way_elevated_removal_needs_upper_level,
+	test_way_elevated_scenario_rule,
+	test_way_elevated_over_station,
+	test_way_elevated_over_bridge_deck,
+	test_way_elevated_maintenance,
+	test_way_elevated_flat_monorail_stays_on_ground,
+	test_way_elevated_runway_is_not_elevated,
+	test_way_elevated_all_descriptors,
 	test_way_road_build_single_tile,
 	test_way_road_build_straight,
 	test_way_road_build_bend,
diff --git a/tests/tests/test_way_elevated.nut b/tests/tests/test_way_elevated.nut
new file mode 100644
index 000000000..15217faa5
--- /dev/null
+++ b/tests/tests/test_way_elevated.nut
@@ -0,0 +1,732 @@
+//
+// This file is part of the Simutrans project under the Artistic License.
+// (see LICENSE.txt)
+//
+
+
+//
+// Tests for building elevated ways.
+//
+// Every expected value here was measured on an unmodified build before it was written
+// down. Where the observed behaviour looks wrong the test still asserts it and the comment
+// says so, because changing any of it belongs in its own patch.
+//
+// An elevated way is a descriptor whose system type is type_elevated and whose waytype is
+// not air_wt. An air way with the same system type is a runway and is built on the ground
+// like any flat way - the last two tests are that pair, because the two halves of the
+// condition are easy to separate by accident.
+//
+// Pakset assumption: the level an elevated way reaches is way_height_clearance above the
+// ground. That is the offset is_valid_pos uses to look for the monorailboden, and it was
+// verified on pak64, where the clearance is 1 and the way lands on level 1. The tests read
+// the clearance from the world rather than writing 1, but they have only ever been run
+// against a pakset where it is 1.
+//
+
+const ELEV_MAX_Z = 4
+
+
+// The height at which a way of this type sits on this square, or -1 if there is none.
+function ELEV_HEIGHT_OF(x, y, waytype)
+{
+	for (local z = 0; z <= ELEV_MAX_Z; z++) {
+		local t = square_x(x, y).get_tile_at_height(z)
+		if (t != null  &&  t.is_valid()  &&  t.has_way(waytype)) {
+			return z
+		}
+	}
+	return -1
+}
+
+
+// A row as a compact string: the height of the way on each tile, or '.' where there is
+// none. A failure then shows the shape of what was built instead of one tripped value.
+function ELEV_ROW(y, x0, x1, waytype)
+{
+	local s = ""
+	for (local x = x0; x <= x1; x++) {
+		local h = ELEV_HEIGHT_OF(x, y, waytype)
+		s += h < 0 ? "." : ("" + h)
+	}
+	return s
+}
+
+
+function ELEV_COL(x, y0, y1, waytype)
+{
+	local s = ""
+	for (local y = y0; y <= y1; y++) {
+		local h = ELEV_HEIGHT_OF(x, y, waytype)
+		s += h < 0 ? "." : ("" + h)
+	}
+	return s
+}
+
+
+// Every tile of the map carrying this waytype, as "(x,y,z)". Used where the shape of a
+// route matters, and as the check that a test left nothing behind.
+function ELEV_ALL(waytype)
+{
+	local s = ""
+	for (local y = 0; y < 16; y++) {
+		for (local x = 0; x < 16; x++) {
+			local h = ELEV_HEIGHT_OF(x, y, waytype)
+			if (h >= 0) { s += "(" + x + "," + y + "," + h + ")" }
+		}
+	}
+	return s
+}
+
+
+// A removal drag follows a route just as the builder does, so it stops wherever that route
+// cannot be walked - across a double slope, or along a staircase - and leaves the way
+// standing. Cleanup that has to be reliable is therefore done tile by tile.
+function ELEV_WIPE_ROW(pl, y, waytype)
+{
+	for (local x = 0; x < 16; x++) {
+		for (local z = 0; z <= ELEV_MAX_Z; z++) {
+			command_x(tool_remove_way).work(pl, coord3d(x, y, z), coord3d(x, y, z), "" + waytype)
+		}
+	}
+}
+
+
+function ELEV_WIPE_ALL(pl, waytype)
+{
+	for (local y = 0; y < 16; y++) {
+		ELEV_WIPE_ROW(pl, y, waytype)
+	}
+}
+
+
+function ELEV_WAY()
+{
+	local l = way_desc_x.get_available_ways(wt_monorail, st_elevated)
+	return l.len() > 0 ? l[0] : null
+}
+
+
+function ELEV_ROAD()
+{
+	local best = null
+	foreach (w in way_desc_x.get_available_ways(wt_road, st_flat)) {
+		if (best == null  ||  w.get_topspeed() < best.get_topspeed()) { best = w }
+	}
+	return best
+}
+
+
+// The level an elevated way reaches from flat ground - see the pakset note at the top.
+function ELEV_LEVEL()
+{
+	return settings.get_way_height_clearance()
+}
+
+
+// The expected picture of a five tile run with one empty tile on each side.
+function ELEV_RUN5(z)
+{
+	return "." + z + z + z + z + z + "."
+}
+
+
+//
+// A straight run west to east. The baseline the later tests vary from.
+//
+function test_way_elevated_build_straight_we()
+{
+	local pl  = player_x(0)
+	local ele = ELEV_WAY()
+	local z   = ELEV_LEVEL()
+	ASSERT_TRUE(ele != null)
+
+	ASSERT_EQUAL(command_x.build_way(pl, coord3d(2, 8, 0), coord3d(6, 8, 0), ele, true), null)
+
+	ASSERT_EQUAL(ELEV_ROW(8, 1, 7, wt_monorail), ELEV_RUN5(z))
+	ASSERT_FALSE(square_x(4, 8).get_tile_at_height(0).has_way(wt_monorail))
+
+	// the level matters here too - see test_way_elevated_removal_needs_upper_level
+	command_x(tool_remove_way).work(pl, coord3d(2, 8, z), coord3d(6, 8, z), "" + wt_monorail)
+	ASSERT_EQUAL(ELEV_ALL(wt_monorail), "")
+	RESET_ALL_PLAYER_FUNDS()
+}
+
+
+//
+// The same run ordered from the other end gives the same five tiles.
+//
+function test_way_elevated_build_straight_ew()
+{
+	local pl  = player_x(0)
+	local ele = ELEV_WAY()
+	local z   = ELEV_LEVEL()
+
+	ASSERT_EQUAL(command_x.build_way(pl, coord3d(6, 8, 0), coord3d(2, 8, 0), ele, true), null)
+	ASSERT_EQUAL(ELEV_ROW(8, 1, 7, wt_monorail), ELEV_RUN5(z))
+
+	command_x(tool_remove_way).work(pl, coord3d(2, 8, z), coord3d(6, 8, z), "" + wt_monorail)
+	ASSERT_EQUAL(ELEV_ALL(wt_monorail), "")
+	RESET_ALL_PLAYER_FUNDS()
+}
+
+
+//
+// North to south, so a fault that only affects one axis cannot hide.
+//
+function test_way_elevated_build_straight_ns()
+{
+	local pl  = player_x(0)
+	local ele = ELEV_WAY()
+	local z   = ELEV_LEVEL()
+
+	ASSERT_EQUAL(command_x.build_way(pl, coord3d(4, 2, 0), coord3d(4, 6, 0), ele, true), null)
+	ASSERT_EQUAL(ELEV_COL(4, 1, 7, wt_monorail), ELEV_RUN5(z))
+
+	command_x(tool_remove_way).work(pl, coord3d(4, 2, z), coord3d(4, 6, z), "" + wt_monorail)
+	ASSERT_EQUAL(ELEV_ALL(wt_monorail), "")
+	RESET_ALL_PLAYER_FUNDS()
+}
+
+
+//
+// A drag that is not straight is routed, and the route it picks is a staircase rather than
+// an L. The exact tiles are asserted because the shape is the thing under test.
+//
+function test_way_elevated_build_bend()
+{
+	local pl  = player_x(0)
+	local ele = ELEV_WAY()
+	local z   = ELEV_LEVEL()
+
+	ASSERT_EQUAL(command_x.build_way(pl, coord3d(2, 8, 0), coord3d(6, 12, 0), ele, false), null)
+
+	ASSERT_EQUAL(ELEV_ALL(wt_monorail),
+		"(2,8," + z + ")(3,8," + z + ")(4,8," + z + ")" +
+		"(4,9," + z + ")(5,9," + z + ")" +
+		"(5,10," + z + ")(6,10," + z + ")" +
+		"(6,11," + z + ")(6,12," + z + ")")
+
+	ELEV_WIPE_ALL(pl, wt_monorail)
+	ASSERT_EQUAL(ELEV_ALL(wt_monorail), "")
+	RESET_ALL_PLAYER_FUNDS()
+}
+
+
+//
+// A drag that starts and ends on the same tile builds nothing and returns the empty error.
+// Recorded rather than judged: whether a one tile elevated way should be possible is a
+// design question, not one for a test that only describes what happens today.
+//
+function test_way_elevated_build_single_tile_refused()
+{
+	local pl  = player_x(0)
+	local ele = ELEV_WAY()
+
+	ASSERT_EQUAL(command_x.build_way(pl, coord3d(4, 8, 0), coord3d(4, 8, 0), ele, true), "")
+	ASSERT_EQUAL(ELEV_ALL(wt_monorail), "")
+	RESET_ALL_PLAYER_FUNDS()
+}
+
+
+//
+// A road on the ground and an elevated way above it share the same tiles.
+//
+function test_way_elevated_over_existing_road()
+{
+	local pl   = player_x(0)
+	local ele  = ELEV_WAY()
+	local road = ELEV_ROAD()
+	local z    = ELEV_LEVEL()
+
+	ASSERT_EQUAL(command_x.build_way(pl, coord3d(2, 8, 0), coord3d(6, 8, 0), road, true), null)
+	ASSERT_EQUAL(command_x.build_way(pl, coord3d(2, 8, 0), coord3d(6, 8, 0), ele, true), null)
+
+	ASSERT_EQUAL(ELEV_ROW(8, 1, 7, wt_road), ".00000.")
+	ASSERT_EQUAL(ELEV_ROW(8, 1, 7, wt_monorail), ELEV_RUN5(z))
+
+	command_x(tool_remove_way).work(pl, coord3d(2, 8, z), coord3d(6, 8, z), "" + wt_monorail)
+	command_x(tool_remove_way).work(pl, coord3d(2, 8, 0), coord3d(6, 8, 0), "" + wt_road)
+	ASSERT_EQUAL(ELEV_ALL(wt_monorail), "")
+	ASSERT_EQUAL(ELEV_ALL(wt_road), "")
+	RESET_ALL_PLAYER_FUNDS()
+}
+
+
+//
+// The same, crossing rather than following: the road runs north to south underneath.
+//
+function test_way_elevated_crosses_road()
+{
+	local pl   = player_x(0)
+	local ele  = ELEV_WAY()
+	local road = ELEV_ROAD()
+	local z    = ELEV_LEVEL()
+
+	ASSERT_EQUAL(command_x.build_way(pl, coord3d(4, 6, 0), coord3d(4, 10, 0), road, true), null)
+	ASSERT_EQUAL(command_x.build_way(pl, coord3d(2, 8, 0), coord3d(6, 8, 0), ele, true), null)
+
+	ASSERT_EQUAL(ELEV_ROW(8, 1, 7, wt_monorail), ELEV_RUN5(z))
+	ASSERT_EQUAL(ELEV_COL(4, 5, 11, wt_road), ".00000.")
+	ASSERT_EQUAL(ELEV_HEIGHT_OF(4, 8, wt_road), 0)
+	ASSERT_EQUAL(ELEV_HEIGHT_OF(4, 8, wt_monorail), z)
+
+	command_x(tool_remove_way).work(pl, coord3d(2, 8, z), coord3d(6, 8, z), "" + wt_monorail)
+	command_x(tool_remove_way).work(pl, coord3d(4, 6, 0), coord3d(4, 10, 0), "" + wt_road)
+	ASSERT_EQUAL(ELEV_ALL(wt_monorail), "")
+	ASSERT_EQUAL(ELEV_ALL(wt_road), "")
+	RESET_ALL_PLAYER_FUNDS()
+}
+
+
+//
+// Two elevated ways of the same type crossing each other on the same level is accepted.
+//
+function test_way_elevated_crosses_elevated()
+{
+	local pl  = player_x(0)
+	local ele = ELEV_WAY()
+	local z   = ELEV_LEVEL()
+
+	ASSERT_EQUAL(command_x.build_way(pl, coord3d(4, 6, 0), coord3d(4, 10, 0), ele, true), null)
+	ASSERT_EQUAL(command_x.build_way(pl, coord3d(2, 8, 0), coord3d(6, 8, 0), ele, true), null)
+
+	ASSERT_EQUAL(ELEV_ROW(8, 1, 7, wt_monorail), ELEV_RUN5(z))
+	ASSERT_EQUAL(ELEV_COL(4, 5, 11, wt_monorail), ELEV_RUN5(z))
+
+	command_x(tool_remove_way).work(pl, coord3d(2, 8, z), coord3d(6, 8, z), "" + wt_monorail)
+	command_x(tool_remove_way).work(pl, coord3d(4, 6, z), coord3d(4, 10, z), "" + wt_monorail)
+	ASSERT_EQUAL(ELEV_ALL(wt_monorail), "")
+	RESET_ALL_PLAYER_FUNDS()
+}
+
+
+//
+// Water under the route does not stop it: the deck passes above.
+//
+function test_way_elevated_over_water()
+{
+	local pl  = player_x(0)
+	local ele = ELEV_WAY()
+	local z   = ELEV_LEVEL()
+
+	command_x(tool_set_climate).work(pl, coord3d(4, 8, 0), coord3d(4, 8, 0), "" + cl_water)
+	ASSERT_TRUE(square_x(4, 8).get_tile_at_height(0).is_water())
+
+	ASSERT_EQUAL(command_x.build_way(pl, coord3d(2, 8, 0), coord3d(6, 8, 0), ele, true), null)
+	ASSERT_EQUAL(ELEV_ROW(8, 1, 7, wt_monorail), ELEV_RUN5(z))
+
+	command_x(tool_remove_way).work(pl, coord3d(2, 8, z), coord3d(6, 8, z), "" + wt_monorail)
+	command_x(tool_set_climate).work(pl, coord3d(4, 8, 0), coord3d(4, 8, 0), "" + cl_mediterran)
+	ASSERT_FALSE(square_x(4, 8).get_tile_at_height(0).is_water())
+	ASSERT_EQUAL(ELEV_ALL(wt_monorail), "")
+	RESET_ALL_PLAYER_FUNDS()
+}
+
+
+//
+// The run may even start on a water tile.
+//
+function test_way_elevated_start_on_water()
+{
+	local pl  = player_x(0)
+	local ele = ELEV_WAY()
+	local z   = ELEV_LEVEL()
+
+	command_x(tool_set_climate).work(pl, coord3d(2, 8, 0), coord3d(2, 8, 0), "" + cl_water)
+
+	ASSERT_EQUAL(command_x.build_way(pl, coord3d(2, 8, 0), coord3d(6, 8, 0), ele, true), null)
+	ASSERT_EQUAL(ELEV_ROW(8, 1, 7, wt_monorail), ELEV_RUN5(z))
+
+	command_x(tool_remove_way).work(pl, coord3d(2, 8, z), coord3d(6, 8, z), "" + wt_monorail)
+	command_x(tool_set_climate).work(pl, coord3d(2, 8, 0), coord3d(2, 8, 0), "" + cl_mediterran)
+	ASSERT_EQUAL(ELEV_ALL(wt_monorail), "")
+	RESET_ALL_PLAYER_FUNDS()
+}
+
+
+//
+// A single slope in the middle of the run refuses the whole drag.
+//
+// Note the asymmetry with test_way_elevated_over_double_slope below, which succeeds over a
+// slope twice as steep on the same tile. Measured, not explained. Both are recorded as
+// they are; whether the single slope refusal is intended is a separate question.
+//
+function test_way_elevated_over_single_slope_refused()
+{
+	local pl  = player_x(0)
+	local ele = ELEV_WAY()
+
+	ASSERT_EQUAL(command_x.set_slope(pl, coord3d(4, 8, 0), slope.east), null)
+	ASSERT_EQUAL(tile_x(4, 8, 0).get_slope(), slope.east)
+
+	ASSERT_EQUAL(command_x.build_way(pl, coord3d(2, 8, 0), coord3d(6, 8, 0), ele, true), "")
+	ASSERT_EQUAL(ELEV_ALL(wt_monorail), "")
+
+	command_x.set_slope(pl, coord3d(4, 8, 0), slope.flat)
+	ASSERT_EQUAL(tile_x(4, 8, 0).get_slope(), 0)
+	RESET_ALL_PLAYER_FUNDS()
+}
+
+
+//
+// A double slope under the route does not refuse it. See the note above.
+//
+function test_way_elevated_over_double_slope()
+{
+	local pl  = player_x(0)
+	local ele = ELEV_WAY()
+	local z   = ELEV_LEVEL()
+
+	ASSERT_EQUAL(command_x.set_slope(pl, coord3d(4, 8, 0), 2 * slope.east), null)
+	ASSERT_EQUAL(tile_x(4, 8, 0).get_slope(), 2 * slope.east)
+
+	ASSERT_EQUAL(command_x.build_way(pl, coord3d(2, 8, 0), coord3d(6, 8, 0), ele, true), null)
+	ASSERT_EQUAL(ELEV_ROW(8, 1, 7, wt_monorail), ELEV_RUN5(z))
+
+	ELEV_WIPE_ROW(pl, 8, wt_monorail)
+	command_x.set_slope(pl, coord3d(4, 8, 0), slope.flat)
+	ASSERT_EQUAL(tile_x(4, 8, 0).get_slope(), 0)
+	ASSERT_EQUAL(ELEV_ALL(wt_monorail), "")
+	RESET_ALL_PLAYER_FUNDS()
+}
+
+
+//
+// A slope on the first tile is accepted, unlike one in the middle.
+//
+function test_way_elevated_start_on_slope()
+{
+	local pl  = player_x(0)
+	local ele = ELEV_WAY()
+	local z   = ELEV_LEVEL()
+
+	ASSERT_EQUAL(command_x.set_slope(pl, coord3d(2, 8, 0), slope.east), null)
+
+	ASSERT_EQUAL(command_x.build_way(pl, coord3d(2, 8, 0), coord3d(6, 8, 0), ele, true), null)
+	ASSERT_EQUAL(ELEV_ROW(8, 1, 7, wt_monorail), ELEV_RUN5(z))
+
+	ELEV_WIPE_ROW(pl, 8, wt_monorail)
+	command_x.set_slope(pl, coord3d(2, 8, 0), slope.flat)
+	ASSERT_EQUAL(ELEV_ALL(wt_monorail), "")
+	RESET_ALL_PLAYER_FUNDS()
+}
+
+
+//
+// The first and last rows and the first column all accept an elevated way. A runway is
+// refused next to the border, which test_way_runway establishes; there is no such rule
+// here.
+//
+function test_way_elevated_at_map_edge()
+{
+	local pl  = player_x(0)
+	local ele = ELEV_WAY()
+	local z   = ELEV_LEVEL()
+
+	ASSERT_EQUAL(command_x.build_way(pl, coord3d(2, 0, 0), coord3d(6, 0, 0), ele, true), null)
+	ASSERT_EQUAL(ELEV_ROW(0, 1, 7, wt_monorail), ELEV_RUN5(z))
+	command_x(tool_remove_way).work(pl, coord3d(2, 0, z), coord3d(6, 0, z), "" + wt_monorail)
+
+	ASSERT_EQUAL(command_x.build_way(pl, coord3d(2, 15, 0), coord3d(6, 15, 0), ele, true), null)
+	ASSERT_EQUAL(ELEV_ROW(15, 1, 7, wt_monorail), ELEV_RUN5(z))
+	command_x(tool_remove_way).work(pl, coord3d(2, 15, z), coord3d(6, 15, z), "" + wt_monorail)
+
+	ASSERT_EQUAL(command_x.build_way(pl, coord3d(0, 2, 0), coord3d(0, 6, 0), ele, true), null)
+	ASSERT_EQUAL(ELEV_COL(0, 1, 7, wt_monorail), ELEV_RUN5(z))
+	command_x(tool_remove_way).work(pl, coord3d(0, 2, z), coord3d(0, 6, z), "" + wt_monorail)
+
+	ASSERT_EQUAL(ELEV_ALL(wt_monorail), "")
+	RESET_ALL_PLAYER_FUNDS()
+}
+
+
+//
+// The way tool does not check funds. A player a million credits in the red builds five
+// tiles of elevated way at a thousand credits each without being stopped.
+//
+// Recorded, not corrected: tool_build_way_t contains no call to can_afford at all, while
+// the bridge and tunnel tools do, so the difference is at least long standing.
+//
+function test_way_elevated_ignores_funds()
+{
+	local pl  = player_x(0)
+	local ele = ELEV_WAY()
+	local z   = ELEV_LEVEL()
+
+	SET_PLAYER_FUNDS(pl, -100000000)
+	ASSERT_TRUE(pl.get_current_cash() < 0)
+
+	ASSERT_EQUAL(command_x.build_way(pl, coord3d(2, 8, 0), coord3d(6, 8, 0), ele, true), null)
+	ASSERT_EQUAL(ELEV_ROW(8, 1, 7, wt_monorail), ELEV_RUN5(z))
+
+	command_x(tool_remove_way).work(pl, coord3d(2, 8, z), coord3d(6, 8, z), "" + wt_monorail)
+	ASSERT_EQUAL(ELEV_ALL(wt_monorail), "")
+	RESET_ALL_PLAYER_FUNDS()
+}
+
+
+//
+// Another player's elevated way can be neither crossed nor removed.
+//
+function test_way_elevated_foreign_owner()
+{
+	world.create_player(2, 1)
+	local pl  = player_x(0)
+	local pl2 = player_x(2)
+	local ele = ELEV_WAY()
+	local z   = ELEV_LEVEL()
+	ASSERT_TRUE(pl2.is_valid())
+
+	ASSERT_EQUAL(command_x.build_way(pl2, coord3d(4, 6, 0), coord3d(4, 10, 0), ele, true), null)
+
+	// player 0 may not build across it
+	ASSERT_EQUAL(command_x.build_way(pl, coord3d(2, 8, 0), coord3d(6, 8, 0), ele, true), "")
+	ASSERT_EQUAL(ELEV_COL(4, 5, 11, wt_monorail), ELEV_RUN5(z))
+	ASSERT_EQUAL(ELEV_HEIGHT_OF(2, 8, wt_monorail), -1)
+
+	// nor remove it
+	ASSERT_EQUAL(command_x(tool_remove_way).work(pl, coord3d(4, 6, z), coord3d(4, 10, z), "" + wt_monorail), "")
+	ASSERT_EQUAL(ELEV_COL(4, 5, 11, wt_monorail), ELEV_RUN5(z))
+
+	// its owner can
+	command_x(tool_remove_way).work(pl2, coord3d(4, 6, z), coord3d(4, 10, z), "" + wt_monorail)
+	ASSERT_EQUAL(ELEV_ALL(wt_monorail), "")
+	RESET_ALL_PLAYER_FUNDS()
+}
+
+
+//
+// Building the same run again changes nothing; a drag from an existing end continues it.
+//
+function test_way_elevated_rebuild_and_extend()
+{
+	local pl  = player_x(0)
+	local ele = ELEV_WAY()
+	local z   = ELEV_LEVEL()
+
+	ASSERT_EQUAL(command_x.build_way(pl, coord3d(2, 8, 0), coord3d(6, 8, 0), ele, true), null)
+	ASSERT_EQUAL(command_x.build_way(pl, coord3d(2, 8, 0), coord3d(6, 8, 0), ele, true), null)
+	ASSERT_EQUAL(ELEV_ROW(8, 1, 10, wt_monorail), "." + z + z + z + z + z + "....")
+
+	ASSERT_EQUAL(command_x.build_way(pl, coord3d(6, 8, 0), coord3d(9, 8, 0), ele, true), null)
+	ASSERT_EQUAL(ELEV_ROW(8, 1, 10, wt_monorail), "." + z + z + z + z + z + z + z + z + ".")
+
+	command_x(tool_remove_way).work(pl, coord3d(2, 8, z), coord3d(9, 8, z), "" + wt_monorail)
+	ASSERT_EQUAL(ELEV_ALL(wt_monorail), "")
+	RESET_ALL_PLAYER_FUNDS()
+}
+
+
+//
+// A drag whose start is given at the elevated level rather than on the ground is refused.
+// The tool takes ground coordinates and lifts the route itself.
+//
+function test_way_elevated_start_from_upper_level_refused()
+{
+	local pl  = player_x(0)
+	local ele = ELEV_WAY()
+	local z   = ELEV_LEVEL()
+
+	ASSERT_EQUAL(command_x.build_way(pl, coord3d(2, 8, 0), coord3d(6, 8, 0), ele, true), null)
+
+	ASSERT_EQUAL(command_x.build_way(pl, coord3d(6, 8, z), coord3d(6, 11, z), ele, true), "")
+	ASSERT_EQUAL(ELEV_COL(6, 9, 11, wt_monorail), "...")
+
+	command_x(tool_remove_way).work(pl, coord3d(2, 8, z), coord3d(6, 8, z), "" + wt_monorail)
+	ASSERT_EQUAL(ELEV_ALL(wt_monorail), "")
+	RESET_ALL_PLAYER_FUNDS()
+}
+
+
+//
+// Removal is the mirror image: it must be addressed at the level the way is on. Asked for
+// on the ground it returns the empty error and removes nothing, which is easy to mistake
+// for a removal that worked.
+//
+function test_way_elevated_removal_needs_upper_level()
+{
+	local pl  = player_x(0)
+	local ele = ELEV_WAY()
+	local z   = ELEV_LEVEL()
+
+	ASSERT_EQUAL(command_x.build_way(pl, coord3d(2, 8, 0), coord3d(6, 8, 0), ele, true), null)
+
+	ASSERT_EQUAL(command_x(tool_remove_way).work(pl, coord3d(2, 8, 0), coord3d(6, 8, 0), "" + wt_monorail), "")
+	ASSERT_EQUAL(ELEV_ROW(8, 1, 7, wt_monorail), ELEV_RUN5(z))
+
+	ASSERT_EQUAL(command_x(tool_remove_way).work(pl, coord3d(2, 8, z), coord3d(6, 8, z), "" + wt_monorail), null)
+	ASSERT_EQUAL(ELEV_ALL(wt_monorail), "")
+	RESET_ALL_PLAYER_FUNDS()
+}
+
+
+//
+// A scenario rule covering part of the route refuses the whole drag; the same drag outside
+// the rectangle is accepted.
+//
+function test_way_elevated_scenario_rule()
+{
+	local pl  = player_x(0)
+	local ele = ELEV_WAY()
+	local z   = ELEV_LEVEL()
+
+	rules.forbid_way_tool_rect(0, tool_build_way, wt_monorail, "", coord(3, 7), coord(5, 9), "No elevated here")
+
+	ASSERT_EQUAL(command_x.build_way(pl, coord3d(2, 8, 0), coord3d(6, 8, 0), ele, true), "")
+	ASSERT_EQUAL(ELEV_ALL(wt_monorail), "")
+
+	ASSERT_EQUAL(command_x.build_way(pl, coord3d(2, 12, 0), coord3d(6, 12, 0), ele, true), null)
+	ASSERT_EQUAL(ELEV_ROW(12, 1, 7, wt_monorail), ELEV_RUN5(z))
+
+	command_x(tool_remove_way).work(pl, coord3d(2, 12, z), coord3d(6, 12, z), "" + wt_monorail)
+	rules.allow_way_tool_rect(0, tool_build_way, wt_monorail, "", coord(3, 7), coord(5, 9))
+	ASSERT_EQUAL(ELEV_ALL(wt_monorail), "")
+	RESET_ALL_PLAYER_FUNDS()
+}
+
+
+//
+// A station on the ground does not block the deck above it.
+//
+function test_way_elevated_over_station()
+{
+	local pl   = player_x(0)
+	local ele  = ELEV_WAY()
+	local road = ELEV_ROAD()
+	local z    = ELEV_LEVEL()
+
+	local stations = building_desc_x.get_available_stations(building_desc_x.station, wt_road, good_desc_x("Passagiere"))
+	ASSERT_TRUE(stations.len() > 0)
+
+	ASSERT_EQUAL(command_x.build_way(pl, coord3d(2, 8, 0), coord3d(6, 8, 0), road, true), null)
+	ASSERT_EQUAL(command_x.build_station(pl, coord3d(4, 8, 0), stations[0]), null)
+
+	ASSERT_EQUAL(command_x.build_way(pl, coord3d(2, 8, 0), coord3d(6, 8, 0), ele, true), null)
+	ASSERT_EQUAL(ELEV_ROW(8, 1, 7, wt_monorail), ELEV_RUN5(z))
+
+	command_x(tool_remove_way).work(pl, coord3d(2, 8, z), coord3d(6, 8, z), "" + wt_monorail)
+	command_x(tool_remover).work(pl, coord3d(4, 8, 0))
+	command_x(tool_remove_way).work(pl, coord3d(2, 8, 0), coord3d(6, 8, 0), "" + wt_road)
+	ASSERT_EQUAL(ELEV_ALL(wt_monorail), "")
+	ASSERT_EQUAL(ELEV_ALL(wt_road), "")
+	RESET_ALL_PLAYER_FUNDS()
+}
+
+
+//
+// Where a bridge deck already occupies the level the elevated way wants, the way is not
+// refused: it climbs one level higher over that tile and comes back down.
+//
+// This is the only measured case where an elevated way leaves its own level, and it is the
+// branch of is_valid_pos that rejects a tile above which is not a monorailboden. It is
+// also the least obvious behaviour of the whole feature, which is why it is pinned.
+//
+function test_way_elevated_over_bridge_deck()
+{
+	local pl  = player_x(0)
+	local ele = ELEV_WAY()
+	local z   = ELEV_LEVEL()
+
+	local bridge = null
+	foreach (b in bridge_desc_x.get_available_bridges(wt_road)) {
+		if (bridge == null  ||  b.get_topspeed() < bridge.get_topspeed()) { bridge = b }
+	}
+	ASSERT_TRUE(bridge != null)
+
+	ASSERT_EQUAL(command_x.build_bridge(pl, coord3d(3, 8, 0), coord3d(5, 8, 0), bridge), null)
+	ASSERT_EQUAL(ELEV_HEIGHT_OF(4, 8, wt_road), z)
+
+	ASSERT_EQUAL(command_x.build_way(pl, coord3d(2, 8, 0), coord3d(6, 8, 0), ele, true), null)
+	ASSERT_EQUAL(ELEV_ROW(8, 1, 7, wt_monorail), "." + z + z + (z + 1) + z + z + ".")
+
+	ELEV_WIPE_ROW(pl, 8, wt_monorail)
+	ELEV_WIPE_ROW(pl, 8, wt_road)
+	ASSERT_EQUAL(ELEV_ALL(wt_monorail), "")
+	ASSERT_EQUAL(ELEV_ALL(wt_road), "")
+	RESET_ALL_PLAYER_FUNDS()
+}
+
+
+//
+// Maintenance is the descriptor's value once per tile, and it comes back on removal.
+//
+function test_way_elevated_maintenance()
+{
+	local pl  = player_x(0)
+	local ele = ELEV_WAY()
+	local z   = ELEV_LEVEL()
+
+	local before = pl.get_current_maintenance()
+	ASSERT_EQUAL(command_x.build_way(pl, coord3d(2, 8, 0), coord3d(6, 8, 0), ele, true), null)
+	ASSERT_EQUAL(pl.get_current_maintenance() - before, 5 * ele.get_maintenance())
+
+	command_x(tool_remove_way).work(pl, coord3d(2, 8, z), coord3d(6, 8, z), "" + wt_monorail)
+	ASSERT_EQUAL(pl.get_current_maintenance(), before)
+	ASSERT_EQUAL(ELEV_ALL(wt_monorail), "")
+	RESET_ALL_PLAYER_FUNDS()
+}
+
+
+//
+// The contrast that gives the elevated rule its meaning: the same waytype with the flat
+// system type stays on the ground.
+//
+function test_way_elevated_flat_monorail_stays_on_ground()
+{
+	local pl   = player_x(0)
+	local flat = way_desc_x.get_available_ways(wt_monorail, st_flat)
+	ASSERT_TRUE(flat.len() > 0)
+	ASSERT_EQUAL(flat[0].get_system_type(), st_flat)
+
+	ASSERT_EQUAL(command_x.build_way(pl, coord3d(2, 8, 0), coord3d(6, 8, 0), flat[0], true), null)
+	ASSERT_EQUAL(ELEV_ROW(8, 1, 7, wt_monorail), ".00000.")
+
+	command_x(tool_remove_way).work(pl, coord3d(2, 8, 0), coord3d(6, 8, 0), "" + wt_monorail)
+	ASSERT_EQUAL(ELEV_ALL(wt_monorail), "")
+	RESET_ALL_PLAYER_FUNDS()
+}
+
+
+//
+// The other half of the condition: an air way whose system type is type_elevated is a
+// runway and is built on the ground. If a change ever tests the system type without also
+// testing the waytype, this is the test that notices.
+//
+function test_way_elevated_runway_is_not_elevated()
+{
+	local pl      = player_x(0)
+	local runways = way_desc_x.get_available_ways(wt_air, st_elevated)
+	ASSERT_TRUE(runways.len() > 0)
+	ASSERT_EQUAL(runways[0].get_system_type(), st_elevated)
+	ASSERT_EQUAL(runways[0].get_waytype(), wt_air)
+
+	// away from the map border, which test_way_runway shows is refused
+	ASSERT_EQUAL(command_x.build_way(pl, coord3d(5, 5, 0), coord3d(5, 10, 0), runways[0], true), null)
+	ASSERT_EQUAL(ELEV_COL(5, 5, 10, wt_air), "000000")
+
+	command_x(tool_remove_way).work(pl, coord3d(5, 5, 0), coord3d(5, 10, 0), "" + wt_air)
+	ASSERT_EQUAL(ELEV_ALL(wt_air), "")
+	RESET_ALL_PLAYER_FUNDS()
+}
+
+
+//
+// Every elevated descriptor the pakset offers behaves the same way, so the tests above are
+// not pinning the quirk of one of them.
+//
+function test_way_elevated_all_descriptors()
+{
+	local pl  = player_x(0)
+	local z   = ELEV_LEVEL()
+	local all = way_desc_x.get_available_ways(wt_monorail, st_elevated)
+	ASSERT_TRUE(all.len() > 0)
+
+	foreach (w in all) {
+		ASSERT_EQUAL(w.get_system_type(), st_elevated)
+		ASSERT_EQUAL(command_x.build_way(pl, coord3d(2, 8, 0), coord3d(6, 8, 0), w, true), null)
+		ASSERT_EQUAL(ELEV_ROW(8, 1, 7, wt_monorail), ELEV_RUN5(z))
+		command_x(tool_remove_way).work(pl, coord3d(2, 8, z), coord3d(6, 8, z), "" + wt_monorail)
+		ASSERT_EQUAL(ELEV_ALL(wt_monorail), "")
+	}
+	RESET_ALL_PLAYER_FUNDS()
+}
