diff --git a/game/modules/tome/class/Actor.lua b/game/modules/tome/class/Actor.lua
index ef456201074e9338c4bf6de40a89ccdc5e139b51..fca1dcffe7a795b08099c997fac48a8240971818 100644
--- a/game/modules/tome/class/Actor.lua
+++ b/game/modules/tome/class/Actor.lua
@@ -3241,6 +3241,8 @@ end
 --- Called upon dropping an object
 function _M:onDropObject(o)
 	if self:attr("has_transmo") then o.__transmo = false end
+	if self.player then game.level.map.attrs(self.x, self.y, "obj_seen", true)
+	elseif game.level.map.attrs(self.x, self.y, "obj_seen") then game.level.map.attrs(self.x, self.y, "obj_seen", false) end
 end
 
 function _M:transmoPricemod(o) if o.type == "gem" then return 0.40 else return 0.05 end end
diff --git a/game/modules/tome/class/Player.lua b/game/modules/tome/class/Player.lua
index 911510c844d56a593e8658df2b9e5ff3d5fd5032..f27bec39d8f0befa6d57a5521478e16b65f0cf23 100644
--- a/game/modules/tome/class/Player.lua
+++ b/game/modules/tome/class/Player.lua
@@ -724,6 +724,10 @@ function _M:runStopped()
 			node.actor:addParticles(engine.Particles.new("notice_enemy", 1))
 		end
 	end
+
+	-- if you stop at an object (such as on a trap), then mark it as seen
+	local obj = game.level.map:getObject(x, y, 1)
+	if obj then game.level.map.attrs(x, y, "obj_seen", true) end
 end
 
 --- Activates a hotkey with a type "inventory"
diff --git a/game/modules/tome/class/interface/PlayerExplore.lua b/game/modules/tome/class/interface/PlayerExplore.lua
index e26e2ee8fef00083470be11d3db45d10ac3ecba3..004d8360fddc394c75926129a9be2ad200db1412 100644
--- a/game/modules/tome/class/interface/PlayerExplore.lua
+++ b/game/modules/tome/class/interface/PlayerExplore.lua
@@ -19,8 +19,6 @@
 
 --- This file implements auto-explore whereby a single command can explore unseen tiles and objects,
 -- go to unexplored doors, and go to the level exit all while avoiding known traps and water if possible.
--- Implemented hastily by "tiger_eye", so please direct all complaints and code criticisms to the ToME
--- forum where they can be promptly ignored ;) (I jest--compliments and suggestions will be most welcome!)
 --
 -- Note that the floodfill algorithm in this file can handle grids with different movement costs
 
@@ -39,15 +37,16 @@ local function toDouble(c)
 	return c - y * game.level.map.w, y
 end
 
-local function listAdjacentTiles(node, no_diagonal, no_cardinal)
+-- a flexible but slow function to list all adjacent tile
+local function listAdjacentTiles(tile, no_diagonal, no_cardinal)
 	local tiles = {}
 	local x, y, c, val
-	if type(node) == "table" then
-		x, y, c, val = unpack(node)
+	if type(tile) == "table" then
+		x, y, c, val = unpack(tile)
 		val = val + 1
-	elseif type(node) == "number" then
-		x, y = toDouble(node)
-		c = node
+	elseif type(tile) == "number" then
+		x, y = toDouble(tile)
+		c = tile
 		val = 1
 	else
 		return tiles
@@ -74,12 +73,15 @@ local function listAdjacentTiles(node, no_diagonal, no_cardinal)
 end
 
 -- Performing a flood-fill algorithm in lua with robust logic is going to be relatively slow, so we
--- need to make things more efficient wherever we can.  "adjacentTiles" below is an example of this.
+-- need to make things more efficient wherever we can.  "getNextNodes" below is an example of this.
 -- Every node knows from which direction it was explored, and it only explores adjacent tiles that
 -- may not have previously been explored.  Nodes that were explored from a cardinal direction only
 -- have three new adjacent tiles to iterate over, and diagonal directions have five new tiles.
 -- Therefore, we should favor cardinal direction tile propagation for speed whenever possible.
-local adjacentTiles = {
+--
+-- Note: if we want this to be faster such as using a floodfill for NPCs (better ai!), then we should
+-- perform the floodfill in C, where we could use more advanced tricks to make it blazingly fast.
+local getNextNodes = {
 	-- Dir 1
 	function(node, cardinal_tiles, diagonal_tiles)
 		local x, y, c, val = node[1], node[2], node[3], node[4]+1
@@ -205,8 +207,394 @@ local adjacentTiles = {
 	end
 }
 
+-- Use directional information to list all adjacent tiles more efficiently
+local listAdjacentNodes = {
+	-- Dir 1
+	function(node)
+		local x, y, c, val = node[1], node[2], node[3], node[4]+1
+		local tiles = {{x + 1, y,     c + 1,                    val, 6 },
+		               {x,     y - 1, c     - game.level.map.w, val, 8 },
+		               {x + 1, y - 1, c + 1 - game.level.map.w, val, 9 }}
+		if y < game.level.map.h - 1 then
+			tiles[4]         = {x,     y + 1, c     + game.level.map.w, val, 2 }
+			tiles[5]         = {x + 1, y + 1, c + 1 + game.level.map.w, val, 3 }
+			if x > 0 then
+				tiles[6] = {x - 1, y + 1, c - 1 + game.level.map.w, val, 1 }
+				tiles[7] = {x - 1, y,     c - 1,                    val, 4 }
+				tiles[8] = {x - 1, y - 1, c - 1 - game.level.map.w, val, 7 }
+			end
+		elseif x > 0 then
+			tiles[4]         = {x - 1, y,     c - 1,                    val, 4 }
+			tiles[5]         = {x - 1, y - 1, c - 1 - game.level.map.w, val, 7 }
+		end
+		return tiles
+	end,
+	-- Dir 2
+	function(node)
+		local x, y, c, val = node[1], node[2], node[3], node[4]+1
+		local tiles = {{x,     y - 1, c     - game.level.map.w, val, 8 }}
+
+		if y < game.level.map.h - 1 then
+			tiles[2] = {x,     y + 1, c     + game.level.map.w, val, 2 }
+			if x > 0 then
+				tiles[3] = {x - 1, y + 1, c - 1 + game.level.map.w, val, 1 }
+				tiles[4] = {x - 1, y,     c - 1,                    val, 4 }
+				tiles[5] = {x - 1, y - 1, c - 1 - game.level.map.w, val, 7 }
+			end
+			if x < game.level.map.w - 1 then
+				tiles[#tiles+1] = {x + 1, y + 1, c + 1 + game.level.map.w, val, 3 }
+				tiles[#tiles+1] = {x + 1, y,     c + 1,                    val, 6 }
+				tiles[#tiles+1] = {x + 1, y - 1, c + 1 - game.level.map.w, val, 9 }
+			end
+		else
+			if x > 0 then
+				tiles[2] = {x - 1, y,     c - 1,                    val, 4 }
+				tiles[3] = {x - 1, y - 1, c - 1 - game.level.map.w, val, 7 }
+			end
+			if x < game.level.map.w - 1 then
+				tiles[#tiles+1] = {x + 1, y,     c + 1,                    val, 6 }
+				tiles[#tiles+1] = {x + 1, y - 1, c + 1 - game.level.map.w, val, 9 }
+			end
+		end
+		return tiles
+	end,
+	-- Dir 3
+	function(node)
+		local x, y, c, val = node[1], node[2], node[3], node[4]+1
+		local tiles = {{x - 1, y,     c - 1,                    val, 4 },
+		               {x - 1, y - 1, c - 1 - game.level.map.w, val, 7 },
+		               {x,     y - 1, c     - game.level.map.w, val, 8 }}
+		if y < game.level.map.h - 1 then
+			tiles[4]         = {x - 1, y + 1, c - 1 + game.level.map.w, val, 1 }
+			tiles[5]         = {x,     y + 1, c     + game.level.map.w, val, 2 }
+			if x < game.level.map.w - 1 then
+				tiles[6] = {x + 1, y + 1, c + 1 + game.level.map.w, val, 3 }
+				tiles[7] = {x + 1, y,     c + 1,                    val, 6 }
+				tiles[8] = {x + 1, y - 1, c + 1 - game.level.map.w, val, 9 }
+			end
+		elseif x < game.level.map.w - 1 then
+			tiles[4]         = {x + 1, y,     c + 1,                    val, 6 }
+			tiles[5]         = {x + 1, y - 1, c + 1 - game.level.map.w, val, 9 }
+		end
+		return tiles
+	end,
+	-- Dir 4
+	function(node)
+		local x, y, c, val = node[1], node[2], node[3], node[4]+1
+		local tiles = {{x + 1, y,     c + 1,                    val, 6 }}
+
+		if x > 0 then
+			tiles[2] = {x - 1, y,     c - 1,                    val, 4 }
+			if y < game.level.map.h - 1 then
+				tiles[3] = {x - 1, y + 1, c - 1 + game.level.map.w, val, 1 }
+				tiles[4] = {x,     y + 1, c     + game.level.map.w, val, 2 }
+				tiles[5] = {x + 1, y + 1, c + 1 + game.level.map.w, val, 3 }
+			end
+			if y > 0 then
+				tiles[#tiles+1] = {x - 1, y - 1, c - 1 - game.level.map.w, val, 7 }
+				tiles[#tiles+1] = {x,     y - 1, c     - game.level.map.w, val, 8 }
+				tiles[#tiles+1] = {x + 1, y - 1, c + 1 - game.level.map.w, val, 9 }
+			end
+		else
+			if y < game.level.map.h - 1 then
+				tiles[2] = {x,     y + 1, c     + game.level.map.w, val, 2 }
+				tiles[3] = {x + 1, y + 1, c + 1 + game.level.map.w, val, 3 }
+			end
+			if y > 0 then
+				tiles[#tiles+1] = {x,     y - 1, c     - game.level.map.w, val, 8 }
+				tiles[#tiles+1] = {x + 1, y - 1, c + 1 - game.level.map.w, val, 9 }
+			end
+		end
+		return tiles
+	end,
+	-- Dir 5
+	function(node)
+		local tiles = {}
+		getNextNodes[5](node, tiles, tiles)
+		return tiles
+	end,
+	-- Dir 6
+	function(node)
+		local x, y, c, val = node[1], node[2], node[3], node[4]+1
+		local tiles = {{x - 1, y,     c - 1,                    val, 4 }}
+
+		if x < game.level.map.w - 1 then
+			tiles[2] = {x + 1, y,     c + 1,                    val, 6 }
+			if y < game.level.map.h - 1 then
+				tiles[3] = {x - 1, y + 1, c - 1 + game.level.map.w, val, 1 }
+				tiles[4] = {x,     y + 1, c     + game.level.map.w, val, 2 }
+				tiles[5] = {x + 1, y + 1, c + 1 + game.level.map.w, val, 3 }
+			end
+			if y > 0 then
+				tiles[#tiles+1] = {x - 1, y - 1, c - 1 - game.level.map.w, val, 7 }
+				tiles[#tiles+1] = {x,     y - 1, c     - game.level.map.w, val, 8 }
+				tiles[#tiles+1] = {x + 1, y - 1, c + 1 - game.level.map.w, val, 9 }
+			end
+		else
+			if y < game.level.map.h - 1 then
+				tiles[2] = {x - 1, y + 1, c - 1 + game.level.map.w, val, 1 }
+				tiles[3] = {x,     y + 1, c     + game.level.map.w, val, 2 }
+			end
+			if y > 0 then
+				tiles[#tiles+1] = {x - 1, y - 1, c - 1 - game.level.map.w, val, 7 }
+				tiles[#tiles+1] = {x,     y - 1, c     - game.level.map.w, val, 8 }
+			end
+		end
+		return tiles
+	end,
+	-- Dir 7
+	function(node)
+		local x, y, c, val = node[1], node[2], node[3], node[4]+1
+		local tiles = {{x,     y + 1, c     + game.level.map.w, val, 2 },
+		               {x + 1, y + 1, c + 1 + game.level.map.w, val, 3 },
+		               {x + 1, y,     c + 1,                    val, 6 }}
+		if x > 0 then
+			tiles[4]         = {x - 1, y + 1, c - 1 + game.level.map.w, val, 1 }
+			tiles[5]         = {x - 1, y,     c - 1,                    val, 4 }
+			if y > 0 then
+				tiles[6] = {x - 1, y - 1, c - 1 - game.level.map.w, val, 7 }
+				tiles[7] = {x,     y - 1, c     - game.level.map.w, val, 8 }
+				tiles[8] = {x + 1, y - 1, c + 1 - game.level.map.w, val, 9 }
+			end
+		elseif y > 0 then
+			tiles[4]         = {x,     y - 1, c     - game.level.map.w, val, 8 }
+			tiles[5]         = {x + 1, y - 1, c + 1 - game.level.map.w, val, 9 }
+		end
+		return tiles
+	end,
+	-- Dir 8
+	function(node)
+		local x, y, c, val = node[1], node[2], node[3], node[4]+1
+		local tiles = {{x,     y + 1, c     + game.level.map.w, val, 2 }}
+
+		if y > 0 then
+			tiles[2] = {x,     y - 1, c     - game.level.map.w, val, 8 }
+			if x > 0 then
+				tiles[3] = {x - 1, y + 1, c - 1 + game.level.map.w, val, 1 }
+				tiles[4] = {x - 1, y,     c - 1,                    val, 4 }
+				tiles[5] = {x - 1, y - 1, c - 1 - game.level.map.w, val, 7 }
+			end
+			if x < game.level.map.w - 1 then
+				tiles[#tiles+1] = {x + 1, y + 1, c + 1 + game.level.map.w, val, 3 }
+				tiles[#tiles+1] = {x + 1, y,     c + 1,                    val, 6 }
+				tiles[#tiles+1] = {x + 1, y - 1, c + 1 - game.level.map.w, val, 9 }
+			end
+		else
+			if x > 0 then
+				tiles[2] = {x - 1, y + 1, c - 1 + game.level.map.w, val, 1 }
+				tiles[3] = {x - 1, y,     c - 1,                    val, 4 }
+			end
+			if x < game.level.map.w - 1 then
+				tiles[#tiles+1] = {x + 1, y + 1, c + 1 + game.level.map.w, val, 3 }
+				tiles[#tiles+1] = {x + 1, y,     c + 1,                    val, 6 }
+			end
+		end
+		return tiles
+	end,
+	-- Dir 9
+	function(node)
+		local x, y, c, val = node[1], node[2], node[3], node[4]+1
+		local tiles = {{x - 1, y + 1, c - 1 + game.level.map.w, val, 1 },
+		               {x,     y + 1, c     + game.level.map.w, val, 2 },
+		               {x - 1, y,     c - 1,                    val, 4 }}
+		if x < game.level.map.w - 1 then
+			tiles[4]         = {x + 1, y + 1, c + 1 + game.level.map.w, val, 3 }
+			tiles[5]         = {x + 1, y,     c + 1,                    val, 6 }
+			if y > 0 then
+				tiles[6] = {x - 1, y - 1, c - 1 - game.level.map.w, val, 7 }
+				tiles[7] = {x,     y - 1, c     - game.level.map.w, val, 8 }
+				tiles[8] = {x + 1, y - 1, c + 1 - game.level.map.w, val, 9 }
+			end
+		elseif y > 0 then
+			tiles[4]         = {x - 1, y - 1, c - 1 - game.level.map.w, val, 7 }
+			tiles[5]         = {x,     y - 1, c     - game.level.map.w, val, 8 }
+		end
+		return tiles
+	end
+}
+
+-- List tiles that are adjacent to both current tile and previous tile that the previous tile iterated over.
+-- Right now these (and "listSharedNodesPrevious") are used to infer what might be a wall, and may be useful later.
+-- c = current, p = previous     c*    *c*
+-- * = returned tile             *p    .p.
+local listSharedNodes = {
+	-- Dir 1
+	function(node)
+		local x, y, c, val = unpack(node)
+		return {{x + 1, y,     c + 1,                    val, 6 },
+		        {x,     y - 1, c     - game.level.map.w, val, 8 }}
+	end,
+	-- Dir 2
+	function(node)
+		local x, y, c, val = unpack(node)
+		if     x < 1                    then return {{x + 1, y,     c + 1,                    val, 6 }}
+		elseif x > game.level.map.w - 2 then return {{x - 1, y,     c - 1,                    val, 4 }}
+		else return {{x - 1, y,     c - 1,                    val, 4 },
+		             {x + 1, y,     c + 1,                    val, 6 }}
+		end
+	end,
+	-- Dir 3
+	function(node)
+		local x, y, c, val = unpack(node)
+		return {{x - 1, y,     c - 1,                    val, 4 },
+		        {x,     y - 1, c     - game.level.map.w, val, 8 }}
+	end,
+	-- Dir 4
+	function(node)
+		local x, y, c, val = unpack(node)
+		if     y < 1                    then return {{x,     y + 1, c     + game.level.map.w, val, 2 }}
+		elseif y > game.level.map.h - 2 then return {{x,     y - 1, c     - game.level.map.w, val, 8 }}
+		else return {{x,     y + 1, c     + game.level.map.w, val, 2 },
+		             {x,     y - 1, c     - game.level.map.w, val, 8 }}
+		end
+	end,
+	-- Dir 5
+	function(node) return {} end,
+	-- Dir 6
+	function(node)
+		local x, y, c, val = unpack(node)
+		if     y < 1                    then return {{x,     y + 1, c     + game.level.map.w, val, 2 }}
+		elseif y > game.level.map.h - 2 then return {{x,     y - 1, c     - game.level.map.w, val, 8 }}
+		else return {{x,     y + 1, c     + game.level.map.w, val, 2 },
+		             {x,     y - 1, c     - game.level.map.w, val, 8 }}
+		end
+	end,
+	-- Dir 7
+	function(node)
+		local x, y, c, val = unpack(node)
+		return {{x,     y + 1, c     + game.level.map.w, val, 2 },
+		        {x + 1, y,     c + 1,                    val, 6 }}
+	end,
+	-- Dir 8
+	function(node)
+		local x, y, c, val = unpack(node)
+		if     x < 1                    then return {{x + 1, y,     c + 1,                    val, 6 }}
+		elseif x > game.level.map.w - 2 then return {{x - 1, y,     c - 1,                    val, 4 }}
+		else return {{x - 1, y,     c - 1,                    val, 4 },
+		             {x + 1, y,     c + 1,                    val, 6 }}
+		end
+	end,
+	-- Dir 9
+	function(node)
+		local x, y, c, val = unpack(node)
+		return {{x,     y + 1, c     + game.level.map.w, val, 2 },
+		        {x - 1, y,     c - 1,                    val, 4 }}
+	end
+}
+
+-- A partial complement to "listSharedNodes".  "listSharedNodes" and "listSharedNodesPrevious" allow us to easily
+-- check specific configurations, which will come in handy if/when I rewrite the "hack" for exploring large areas.
+-- c = current, p = previous     c.    .c.
+-- * = returned tile             .p    *p*
+local listSharedNodesPrevious = {
+	-- Dir 1
+	function(node) return {} end,
+	-- Dir 2
+	function(node)
+		local x, y, c, val = unpack(node)
+		if     x < 1                    then return {{x + 1, y - 1, c + 1 - game.level.map.w, val, 9 }}
+		elseif x > game.level.map.w - 2 then return {{x - 1, y - 1, c - 1 - game.level.map.w, val, 7 }}
+		else return {{x + 1, y - 1, c + 1 - game.level.map.w, val, 9 },
+		             {x - 1, y - 1, c - 1 - game.level.map.w, val, 7 }}
+		end
+	end,
+	-- Dir 3
+	function(node) return {} end,
+	-- Dir 4
+	function(node)
+		local x, y, c, val = unpack(node)
+		if     y < 1                    then return {{x + 1, y + 1, c + 1 + game.level.map.w, val, 3 }}
+		elseif y > game.level.map.h - 2 then return {{x + 1, y - 1, c + 1 - game.level.map.w, val, 9 }}
+		else return {{x + 1, y + 1, c + 1 + game.level.map.w, val, 3 },
+		             {x + 1, y - 1, c + 1 - game.level.map.w, val, 9 }}
+		end
+	end,
+	-- Dir 5
+	function(node) return {} end,
+	-- Dir 6
+	function(node)
+		local x, y, c, val = unpack(node)
+		if     y < 1                    then return {{x - 1, y + 1, c - 1 + game.level.map.w, val, 1 }}
+		elseif y > game.level.map.h - 2 then return {{x - 1, y - 1, c - 1 - game.level.map.w, val, 7 }}
+		else return {{x - 1, y + 1, c - 1 + game.level.map.w, val, 1 },
+		             {x - 1, y - 1, c - 1 - game.level.map.w, val, 7 }}
+		end
+	end,
+	-- Dir 7
+	function(node) return {} end,
+	-- Dir 8
+	function(node)
+		local x, y, c, val = unpack(node)
+		if     x < 1                    then return {{x + 1, y + 1, c + 1 + game.level.map.w, val, 3 }}
+		elseif x > game.level.map.w - 2 then return {{x - 1, y + 1, c - 1 + game.level.map.w, val, 1 }}
+		else return {{x + 1, y + 1, c + 1 + game.level.map.w, val, 3 },
+		             {x - 1, y + 1, c - 1 + game.level.map.w, val, 1 }}
+		end
+	end,
+	-- Dir 9
+	function(node) return {} end
+}
+
+local previousNode = {
+	-- Dir 1
+	function(node) local x, y, c, val = unpack(node) ; return {x + 1, y - 1, c + 1 - game.level.map.w, val-1, 9 } end,
+	-- Dir 2
+	function(node) local x, y, c, val = unpack(node) ; return {x,     y - 1, c     - game.level.map.w, val-1, 8 } end,
+	-- Dir 3
+	function(node) local x, y, c, val = unpack(node) ; return {x - 1, y - 1, c - 1 - game.level.map.w, val-1, 7 } end,
+	-- Dir 4
+	function(node) local x, y, c, val = unpack(node) ; return {x + 1, y,     c + 1,                    val-1, 6 } end,
+	-- Dir 5
+	function(node) local x, y, c, val = unpack(node) ; return {x,     y,     c,                        val-1, 5 } end,
+	-- Dir 6
+	function(node) local x, y, c, val = unpack(node) ; return {x - 1, y,     c - 1,                    val-1, 4 } end,
+	-- Dir 7
+	function(node) local x, y, c, val = unpack(node) ; return {x + 1, y + 1, c + 1 + game.level.map.w, val-1, 3 } end,
+	-- Dir 8
+	function(node) local x, y, c, val = unpack(node) ; return {x,     y + 1, c     + game.level.map.w, val-1, 2 } end,
+	-- Dir 9
+	function(node) local x, y, c, val = unpack(node) ; return {x - 1, y + 1, c - 1 + game.level.map.w, val-1, 1 } end
+}
+
+-- One more kindness to the player: take advantage of asymmetric LoS in this one specific case.
+-- If an enemy is at '?', the player is able to prevent an ambush by moving to 'x' instead of 't'.
+-- This is the only sensibly preventable ambush (that I know of) in which the player can move
+-- in a way to see the would-be ambusher and the would-be ambusher can't see the player.
+-- However, don't do this if it will step onto a known trap
+--
+--   .tx      Moving onto 't' puts us adjacent to an unseen tile, '?'
+--   ?#@      --> Pick 'x' instead
+local function checkAmbush(self)
+	if not self.running or not self.running.explore or not self.running.path or not self.running.path[self.running.cnt] then return end
+
+	local cx, cy = self.running.path[self.running.cnt].x, self.running.path[self.running.cnt].y
+	if math.abs(self.x - cx) == 1 and math.abs(self.y - cy) == 1 then
+		if game.level.map:checkAllEntities(self.x, cy, "block_move", self) and not game.level.map:checkAllEntities(cx, self.y, "block_move", self) and
+				game.level.map:isBound(self.x, 2*cy - self.y) and not game.level.map.has_seens(self.x, 2*cy - self.y) then
+			local trap = game.level.map(cx, self.y, Map.TRAP)
+			if not trap or not trap:knownBy(self) then
+				table.insert(self.running.path, self.running.cnt, {x=cx, y=self.y})
+			end
+		elseif game.level.map:checkAllEntities(cx, self.y, "block_move", self) and not game.level.map:checkAllEntities(self.x, cy, "block_move", self) and
+				game.level.map:isBound(2*cx - self.x, self.y) and not game.level.map.has_seens(2*cx - self.x, self.y) then
+			local trap = game.level.map(self.x, cy, Map.TRAP)
+			if not trap or not trap:knownBy(self) then
+				table.insert(self.running.path, self.running.cnt, {x=self.x, y=cy})
+			end
+		end
+	end
+end
+
+-- If a target destination is found, then it creates (or updates) "self.running" and returns true.
+-- If no target, or if we shouldn't explore for some reason, then return false.
 function _M:autoExplore()
+	-- levels that use "all_remembered" (like towns) don't set "has_seen" values to true for all grids,
+	-- so this lets us behave reasonably in these zones: go to objects and then exit
 	local do_unseen = not (game.level.all_remembered or game.zone and game.zone.all_remembered)
+
+	-- if we changed levels, then remove previous auto-explore information
+	if self.running_prev and self.running_prev.level ~= game.level then self.running_prev = nil end
+
 	local node = { self.x, self.y, toSingle(self.x, self.y), 0, 5 }
 	local current_tiles = { node }
 	local unseen_tiles = {}
@@ -230,7 +618,7 @@ function _M:autoExplore()
 
 	-- a few tunable parameters
 	local extra_iters = 5     -- number of extra iterations to do after we found an item or unseen tile
-	local singlet_greed = 5   -- number of additional moves we're willing to do to explore a single unseen tile
+	local singlet_greed = 4   -- number of additional moves we're willing to do to explore a single unseen tile
 	local item_greed = 5      -- number of additional moves we're willing to do to visit an unseen item rather than an unseen tile
 
 	-- Create a distance map array via flood-fill to locate unseen tiles, unvisited items, closed doors, and exits
@@ -241,13 +629,13 @@ function _M:autoExplore()
 		local diagonal_tiles = {}
 		-- Nearly half the time is spent here.  This could be implemented in C if desired, but I think it's fast enough
 		for _, node in ipairs(current_tiles) do
-			adjacentTiles[node[5]](node, cardinal_tiles, diagonal_tiles)
+			getNextNodes[node[5]](node, cardinal_tiles, diagonal_tiles)
 		end
 
 		-- The other half of the time is spent in this loop
 		for _, tile_list in ipairs({cardinal_tiles, diagonal_tiles}) do
 			for _, node in ipairs(tile_list) do
-				local x, y, c, move_cost = unpack(node)
+				local x, y, c, move_cost, dir = unpack(node)
 
 				if not game.level.map.has_seens(x, y) and do_unseen then
 					if not values[c] or values[c] > move_cost then
@@ -258,12 +646,50 @@ function _M:autoExplore()
 						end
 						-- Try to not abandon lone unseen tiles
 						local is_singlet = true
-						for _, anode in ipairs(listAdjacentTiles(node)) do
+						for _, anode in ipairs(listAdjacentNodes[dir](node)) do
 							if not game.level.map.has_seens(anode[1], anode[2]) then
 								is_singlet = false
 								break
 							end
 						end
+
+						-- look for tiles that are probably walls so we can hopefully explore more efficiently by preventing unnecessary return trips.
+						--               ?#       #?#
+						-- For example:  #.  and  ...  are probably walls (in most zones)
+						if not is_singlet then
+							is_singlet = true
+							for _, anode in ipairs(listSharedNodes[dir](node)) do
+								if not game.level.map.has_seens(anode[1], anode[2]) or not game.level.map:checkEntity(anode[1], anode[2], Map.TERRAIN, "does_block_move") then
+									is_singlet = false
+								-- if we propagated diagonally, then check if this might be a wall side, not corner
+								--  c = current,  1 = supposed wall    #c1
+								--  p = previous, 2 = supposed floor   p.2
+								elseif dir % 2 == 1 then
+									local x1 = 2*x - anode[1]
+									local y1 = 2*y - anode[2]
+									if game.level.map.has_seens(x1, y1) and game.level.map:checkEntity(x1, y1, Map.TERRAIN, "does_block_move") then
+										local pnode = previousNode[dir](node)
+										x1 = x1 + pnode[1] - anode[1]
+										y1 = y1 + pnode[2] - anode[2]
+										if game.level.map.has_seens(x1, y1) and not game.level.map:checkEntity(x1, y1, Map.TERRAIN, "does_block_move") then
+											is_singlet = true
+											break
+										end
+									end
+								end
+							end
+							-- if walls are where we expect them to be, check that floors are where we expect:
+							-- c = current, ? = supposed floor   #c#
+							-- p = previous,                     ?p?
+							if is_singlet then
+								for _, anode in ipairs(listSharedNodesPrevious[dir](node)) do
+									if not game.level.map.has_seens(anode[1], anode[2]) or game.level.map:checkEntity(anode[1], anode[2], Map.TERRAIN, "does_block_move") then
+										is_singlet = false
+										break
+									end
+								end
+							end
+						end
 						if is_singlet then
 							unseen_singlets[#unseen_singlets + 1] = c
 						end
@@ -315,7 +741,7 @@ function _M:autoExplore()
 						-- only go to closed doors with unseen grids behind them. We can go through "safe" doors
 						elseif terrain.door_opened and do_unseen then
 							local is_unexplored = false
-							for _, anode in ipairs(listAdjacentTiles(node)) do
+							for _, anode in ipairs(listAdjacentNodes[dir](node)) do
 								if not game.level.map.has_seens(anode[1], anode[2]) then
 									is_unexplored = true
 									break
@@ -339,7 +765,7 @@ function _M:autoExplore()
 						elseif terrain.orb_portal then
 							local is_portal_center = true
 							local is_small_portal = true
-							for _, anode in ipairs(listAdjacentTiles(node)) do
+							for _, anode in ipairs(listAdjacentNodes[dir](node)) do
 								if not game.level.map:checkEntity(anode[1], anode[2], Map.TERRAIN, "orb_portal") then
 									is_portal_center = false
 								else
@@ -408,11 +834,11 @@ function _M:autoExplore()
 		local mindist = 999999999999999
 		-- If we already have a suitable target that we haven't reached yet, then use that as our target.  This will be more useful
 		-- if or when we save info between between instances of running auto-explore.  For now it's useful when dodging traps on the fly.
-		if self.running and (self.running.explore == "exit" or self.running.explore == "portal") and (self.x ~= self.running.target.x or self.y ~= self.running.target.y) then
+		if self.running_prev and (self.running_prev.explore == "exit" or self.running_prev.explore == "portal") and (self.x ~= self.running_prev.target.x or self.y ~= self.running_prev.target.y) then
 			-- verify that the target is currently reachable
 			for _, c in ipairs(exits) do
 				local x, y = toDouble(c)
-				if x == self.running.target.x and y == self.running.target.y then
+				if x == self.running_prev.target.x and y == self.running_prev.target.y then
 					target_type = "exit"
 					choices[1] = c
 					distances[c] = 1
@@ -423,7 +849,7 @@ function _M:autoExplore()
 			if #choices == 0 then
 				for _, c in ipairs(portals) do
 					local x, y = toDouble(c)
-					if x == self.running.target.x and y == self.running.target.y then
+					if x == self.running_prev.target.x and y == self.running_prev.target.y then
 						target_type = "portal"
 						choices[1] = c
 						distances[c] = 1
@@ -433,11 +859,11 @@ function _M:autoExplore()
 				end
 			end
 		end
-		-- try to explore cleanly--don't leave single unseen tiles by themselves
-		if #choices == 0 then
-			for _, c in ipairs(unseen_singlets) do
-				if values[c] <= minval + singlet_greed then
-					target_type = "unseen"
+		-- go to closest items first
+		if #choices == 0 and minval_items <= minval + item_greed then
+			for _, c in ipairs(unseen_items) do
+				if values[c] == minval_items then
+					target_type = "object"
 					choices[#choices + 1] = c
 					local x, y = toDouble(c)
 					local dist = core.fov.distance(self.x, self.y, x, y, true)
@@ -448,11 +874,11 @@ function _M:autoExplore()
 				end
 			end
 		end
-		-- go to closest items first
-		if #choices == 0 and minval_items <= minval + item_greed then
-			for _, c in ipairs(unseen_items) do
-				if values[c] == minval_items then
-					target_type = "object"
+		-- try to explore cleanly--don't leave single unseen tiles by themselves
+		if #choices == 0 then
+			for _, c in ipairs(unseen_singlets) do
+				if values[c] <= minval + singlet_greed then
+					target_type = "unseen"
 					choices[#choices + 1] = c
 					local x, y = toDouble(c)
 					local dist = core.fov.distance(self.x, self.y, x, y, true)
@@ -741,10 +1167,10 @@ function _M:autoExplore()
 					-- don't run into adjacent interesting terrain if we've already been running
 					local x, y = path[1].x, path[1].y
 					local terrain = game.level.map(x, y, Map.TERRAIN)
-					if terrain.notice and (target_type ~= "exit" and target_type ~= "portal" or #path ~= 1 and not game.level.map.attrs(x, y, "noticed")) then
+					if terrain.notice and (target_type ~= "exit" and target_type ~= "portal" or #path ~= 1) then
 						if safe_doors[toSingle(x, y)] and not self.running.busy then
 							self.running.busy = { type = "opening door", do_move = true, no_energy = true }
-						else
+						elseif not game.level.map.attrs(x, y, "noticed") then
 							if terrain.change_level or terrain.orb_portal then game.level.map.attrs(x, y, "noticed", true) end
 							self:runStop("interesting terrain")
 							return false
@@ -759,6 +1185,8 @@ function _M:autoExplore()
 					self.running.ave_x = (self.running.ave_x*self.running.ave_N + 2*(target_x + self.x)) / (self.running.ave_N + 4)
 					self.running.ave_y = (self.running.ave_y*self.running.ave_N + 2*(target_y + self.y)) / (self.running.ave_N + 4)
 					self.running.ave_N = self.running.ave_N + 2
+					-- end hack!
+					checkAmbush(self)
 				else
 					-- another fringe case: if we target an item in an adjacent wall that we've probably already targeted, then mark it as seen and find a new target
 					if #path == 1 and target_type == "object" and game.level.map:checkEntity(target_x, target_y, Map.TERRAIN, "block_move", self, nil, true) then
@@ -776,13 +1204,24 @@ function _M:autoExplore()
 						end, false, true),
 						explore = target_type,
 						target = {x=target_x, y=target_y},
-						-- hack!
-						ave_x = 0.5*(target_x + self.x),
-						ave_y = 0.5*(target_y + self.y),
-						ave_N = 2,
+						level = game.level
 					}
+					-- hack!
+					if self.running_prev then
+						self.running.ave_N = 0.6*self.running_prev.ave_N
+						self.running.ave_x = (self.running_prev.ave_x*self.running.ave_N + 2*(target_x + self.x)) / (self.running.ave_N + 4)
+						self.running.ave_y = (self.running_prev.ave_y*self.running.ave_N + 2*(target_y + self.y)) / (self.running.ave_N + 4)
+						self.running.ave_N = 2*math.floor(0.5*self.running.ave_N + 1)
+					else
+						self.running.ave_x = 0.5*(target_x + self.x)
+						self.running.ave_y = 0.5*(target_y + self.y)
+						self.running.ave_N = 2
+					end
+					-- end hack!
+
 					self.running.dialog.__showup = nil
 					self.running.dialog.__hidden = true
+					self.running_prev = self.running
 
 					self:runStep()
 				end
@@ -860,29 +1299,8 @@ function _M:checkAutoExplore()
 		return self:autoExplore()
 	end
 
-	-- One more kindness to the player: take advantage of asymmetric LoS in this one specific case.
-	-- If an enemy is at '?', the player is able to prevent an ambush by moving to 'x' instead of 't'.
-	-- This is the only sensibly preventable ambush (that I know of) in which the player can move
-	-- in a way to see the would-be ambusher and the would-be ambusher can't see the player.
-	-- However, don't do this if it will step onto a known trap
-	--
-	--   .tx      Moving onto 't' puts us adjacent to an unseen tile, '?'
-	--   ?#@      --> Pick 'x' instead
-	if math.abs(self.x - cx) == 1 and math.abs(self.y - cy) == 1 then
-		if game.level.map:checkAllEntities(self.x, cy, "block_move", self) and not game.level.map:checkAllEntities(cx, self.y, "block_move", self) and
-				game.level.map:isBound(self.x, 2*cy - self.y) and not game.level.map.has_seens(self.x, 2*cy - self.y) then
-			local trap = game.level.map(cx, self.y, Map.TRAP)
-			if not trap or not trap:knownBy(self) then
-				table.insert(self.running.path, self.running.cnt, {x=cx, y=self.y})
-			end
-		elseif game.level.map:checkAllEntities(cx, self.y, "block_move", self) and not game.level.map:checkAllEntities(self.x, cy, "block_move", self) and
-				game.level.map:isBound(2*cx - self.x, self.y) and not game.level.map.has_seens(2*cx - self.x, self.y) then
-			local trap = game.level.map(self.x, cy, Map.TRAP)
-			if not trap or not trap:knownBy(self) then
-				table.insert(self.running.path, self.running.cnt, {x=self.x, y=cy})
-			end
-		end
-	end
+	-- avoid a simple preventable ambush
+	checkAmbush(self)
 
 	-- continue current path if we haven't seen the target tile or object yet
 	if not game.level.map.has_seens(tx, ty) then return true end