diff --git a/game/engines/default/engine/interface/PlayerRun.lua b/game/engines/default/engine/interface/PlayerRun.lua
index 53f27062a9ddc931e55b4a8b8c8e9cf0f1310eb5..91cbd06994a0153744bbe3f24cb803cbb54c0f36 100644
--- a/game/engines/default/engine/interface/PlayerRun.lua
+++ b/game/engines/default/engine/interface/PlayerRun.lua
@@ -128,7 +128,7 @@ function _M:runStep()
 	if not self.running then return false end
 
 	local ret, msg = self:runCheck()
-	if not ret and self.running.cnt > 1 then
+	if not ret and (self.running.cnt > 1 or self.running.busy) then
 		self:runStop(msg)
 		return false
 	else
@@ -139,8 +139,21 @@ function _M:runStep()
 			elseif not self.running.path[self.running.cnt] then
 				self:runStop()
 			else
-				self:move(self.running.path[self.running.cnt].x, self.running.path[self.running.cnt].y)
+				-- Allow auto-explore to perform actions other than movement, which should be performed
+				-- or setup in "checkAutoExplore".  Hence, modules can make auto-explore borg-like if desired.
+				-- For example, non-move actions can be picking up an item, using a talent, resting, etc.
+				-- Some actions can require moving into a tile, such as opening a door or bump-attacking an enemy.
+				-- "self.running.cnt" is not incremented while "self.running.busy" exists.
+				if not self.running.busy or self.running.busy.do_move then
+					self:move(self.running.path[self.running.cnt].x, self.running.path[self.running.cnt].y)
+				end
+				self:runMoved()
+				-- Did not move ? no use in running unless we were busy
+				if self.running and not self.running.busy and self.x == oldx and self.y == oldy then
+					self:runStop("didn't move")
+				end
 			end
+			if not self.running then return false end
 		else
 			-- Try to move around known traps if possible
 			local dir_is_cardinal = self.running.dir == 2 or self.running.dir == 4 or self.running.dir == 6 or self.running.dir == 8
@@ -182,6 +195,10 @@ function _M:runStep()
 			end
 			-- Move!
 			self:moveDir(self.running.dir)
+			self:runMoved()
+			-- Did not move ? no use in running
+			if self.x == oldx and self.y == oldy then self:runStop() end
+			if not self.running then return false end
 
 			if self.running.block_left then self.running.ignore_left = nil end
 			if self.running.ignore_left then
@@ -211,13 +228,12 @@ function _M:runStep()
 			end
 
 		end
-		self:runMoved()
-
-		-- Did not move ? no use in running
-		if self.x == oldx and self.y == oldy then self:runStop() end
-
 		if not self.running then return false end
-		self.running.cnt = self.running.cnt + 1
+		if not self.running.busy then
+			self.running.cnt = self.running.cnt + 1
+		elseif self.running.busy.no_energy then
+			return self:runStep()
+		end
 		return true
 	end
 end
diff --git a/game/modules/tome/class/Player.lua b/game/modules/tome/class/Player.lua
index 32be26a74bc73fd0dd37478934d5a510ee392093..798a9f0a69f7309d083546a17dad6e7f5a27569b 100644
--- a/game/modules/tome/class/Player.lua
+++ b/game/modules/tome/class/Player.lua
@@ -636,9 +636,17 @@ function _M:runCheck(ignore_memory)
 			end
 		end
 
-		-- Only notice interesting terrains, but allow auto-explore to take us to the exit
+		-- Only notice interesting terrains, but allow auto-explore and A* to take us to the exit.  Auto-explore can also take us through "safe" doors
 		local grid = game.level.map(x, y, Map.TERRAIN)
-		if grid and grid.notice and not (what ~= "self" and self.running and self.running.explore == "exit" and #self.running.path == self.running.cnt) then
+		if grid and grid.notice and not (self.running and self.running.path and (what ~= self and
+				(self.running.explore and grid.door_opened                                          -- safe door
+				or #self.running.path == self.running.cnt and (self.running.explore == "exit"       -- auto-explore onto exit
+				or not self.running.explore and grid.change_level))                                 -- A* onto exit
+				or #self.running.path - self.running.cnt < 2 and (self.running.explore == "portal"  -- auto-explore onto portal
+				or not self.running.explore and grid.orb_portal)                                    -- A* onto portal
+				or self.running.cnt < 3 and grid.orb_portal and                                     -- path from portal
+				game.level.map:checkEntity(self.running.path[1].x, self.running.path[1].y, Map.TERRAIN, "orb_portal")))
+		then 
 			noticed = "interesting terrain"; return
 		end
 		if grid and grid.type and grid.type == "store" then noticed = "store entrance spotted"; return end
diff --git a/game/modules/tome/class/interface/PlayerExplore.lua b/game/modules/tome/class/interface/PlayerExplore.lua
index cd5e67abfa70ad81697233b409dcffd6f9abb250..910dd93ce80d6d3ba7b82424ff216763f98cad1b 100644
--- a/game/modules/tome/class/interface/PlayerExplore.lua
+++ b/game/modules/tome/class/interface/PlayerExplore.lua
@@ -214,6 +214,7 @@ function _M:autoExplore()
 	local unseen_items = {}
 	local unseen_doors = {}
 	local exits = {}
+	local portals = {}
 	local values = {}
 	values[node[3]] = 0
 	local door_values = {}
@@ -223,6 +224,7 @@ function _M:autoExplore()
 	local running = true
 	local minval = 999999999999999
 	local minval_items = 999999999999999
+	local minval_portals = 999999999999999
 	local val, _, anode, tile_list
 
 	-- a few tunable parameters
@@ -287,7 +289,6 @@ function _M:autoExplore()
 					if (not values[c] or values[c] > move_cost or is_slow) and (not is_slow or not slow_values[c] or slow_values[c] > move_cost) then
 --						if not game.level.map:checkEntity(x, y, Map.TERRAIN, "block_move", self, nil, true) then
 --						if not game.level.map:checkAllEntities(x, y, "block_move", self) then
---						if not (terrain.does_block_move or terrain.is_door and terrain.door_opened then
 
 						-- This is a sinful man's "block_move".  If it messes up, then players can explore the level themselves!
 						-- (and they can always interrupt running if something terrible happens)
@@ -310,26 +311,45 @@ function _M:autoExplore()
 								minval_items = move_cost
 							end
 						-- default to reasonable targets if there are no accessible unseen tiles or objects left on the map
-						elseif #unseen_tiles == 0 and #unseen_items == 0 then
-							-- only go to closed doors with unseen grids behind them
-							if terrain.door_opened and do_unseen then
-								local is_unexplored = false
-								for _, anode in ipairs(listAdjacentTiles(node)) do
-									if not game.level.map.has_seens(anode[1], anode[2]) then
-										is_unexplored = true
-										break
-									end
+						-- 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
+								if not game.level.map.has_seens(anode[1], anode[2]) then
+									is_unexplored = true
+									break
 								end
-								if is_unexplored then
-									unseen_doors[#unseen_doors + 1] = c
-									if not door_values[c] or door_values[c] > move_cost then
-										door_values[c] = move_cost
-									end
+							end
+							if is_unexplored then
+								unseen_doors[#unseen_doors + 1] = c
+								if not door_values[c] or door_values[c] > move_cost then
+									door_values[c] = move_cost
+								end
+							else -- door is safe to move through
+								node[4] = move_cost + 1
+								values[c] = move_cost + 1
+								current_tiles_next[#current_tiles_next + 1] = node
+							end
+						-- go to next level, exit, previous level, or orb portal (in that order of precedence)
+						elseif terrain.change_level then
+							exits[#exits + 1] = c
+							values[c] = move_cost
+						elseif terrain.orb_portal then
+							local is_portal_center = true
+							local is_small_portal = true
+							for _, anode in ipairs(listAdjacentTiles(node)) do
+								if not game.level.map:checkEntity(anode[1], anode[2], Map.TERRAIN, "orb_portal") then
+									is_portal_center = false
+								else
+									is_small_portal = false
 								end
-							-- go to next level, exit, or previous level (in that order of precedence)
-							elseif terrain.change_level then
-								exits[#exits + 1] = c
+							end
+							if is_portal_center or is_small_portal then
+								portals[#portals + 1] = c
 								values[c] = move_cost
+								if move_cost < minval_portals then
+									minval_portals = move_cost
+								end
 							end
 						end
 					end
@@ -379,7 +399,7 @@ function _M:autoExplore()
 
 	-- Negligible time is spent below
 	-- Choose target
-	if #unseen_tiles > 0 or #unseen_items > 0 or #unseen_doors > 0 or #exits > 0 then
+	if #unseen_tiles > 0 or #unseen_items > 0 or #unseen_doors > 0 or #exits > 0 or #portals > 0 then
 		local target_type
 		local choices = {}
 		local distances = {}
@@ -470,12 +490,36 @@ function _M:autoExplore()
 		end
 		-- if no destination yet, go to nearest unexplored closed non-vault door
 		if #choices == 0 then
+			local add_values = {}
 			for _, c in ipairs(unseen_doors) do
 				local x, y = toDouble(c)
 				local terrain = game.level.map(x, y, Map.TERRAIN)
 				if not terrain.door_player_check then
 					target_type = "door"
 					choices[#choices + 1] = c
+					-- we may take an extra step to approach a door squarely from a cardinal direction, so let's account for this
+					local door_val = door_values[c]
+					local min_diagonal = door_val
+					local min_cardinal = door_val
+					for _, node in ipairs(listAdjacentTiles(c, true)) do
+						if values[node[3]] and values[node[3]] < min_cardinal then
+							min_cardinal = values[node[3]]
+						end
+					end
+					for _, node in ipairs(listAdjacentTiles(c, false, true)) do
+						if values[node[3]] and values[node[3]] < min_diagonal then
+							min_diagonal = values[node[3]]
+						end
+					end
+					if min_cardinal > min_diagonal then
+						for _, node in ipairs(listAdjacentTiles(c, false, true)) do
+							if values[node[3]] then
+								add_values[node[3]] = values[node[3]] + 1
+								door_values[c] = door_val + 1
+							end
+						end
+					end
+
 					local dist = core.fov.distance(self.x, self.y, x, y, true) + 10*door_values[c]
 					distances[c] = dist
 					if dist < mindist then
@@ -483,6 +527,9 @@ function _M:autoExplore()
 					end
 				end
 			end
+			for _, c in ipairs(add_values) do
+				values[c] = values[c] + add_values[c]
+			end
 		end
 		-- ...or vault door
 		if #choices == 0 then
@@ -548,6 +595,21 @@ function _M:autoExplore()
 				end
 			end
 		end
+		-- ...or orb portal
+		if #choices == 0 then
+			for _, c in ipairs(portals) do
+				if values[c] == minval_portals then
+					target_type = "portal"
+					choices[#choices + 1] = c
+					local x, y = toDouble(c)
+					local dist = core.fov.distance(self.x, self.y, x, y, true)
+					distances[c] = dist
+					if dist < mindist then
+						mindist = dist
+					end
+				end
+			end
+		end
 
 		-- if multiple choices, then choose nearest one based on fov distance metric
 		if #choices > 1 then
@@ -633,8 +695,12 @@ function _M:autoExplore()
 			if #path > 0 then
 				if self.running and self.running.explore then
 					-- take care of a couple fringe cases
-					-- don't open adjacent doors if we've already been running
-					if #path == 1 and target_type == "door" then return false end
+					-- don't open adjacent or target doors if we've already been running
+					if target_type == "door" then
+						if #path == 1 then return false
+						else path[#path] = nil end
+					end
+
 					-- don't run into adjacent interesting terrain if we've already been running
 					local terrain = game.level.map(path[1].x, path[1].y, Map.TERRAIN)
 					if terrain.notice and not (#path == 1 and target_type == "exit") then
@@ -655,6 +721,9 @@ function _M:autoExplore()
 						game.level.map.attrs(target_x, target_y, "obj_seen", true)
 						return self:autoExplore()
 					end
+					-- don't open non-adjacent target doors
+					if target_type == "door" and #path > 1 then path[#path] = nil end
+
 					self.running = {
 						path = path,
 						cnt = 1,
@@ -686,26 +755,20 @@ function _M:checkAutoExplore()
 	-- If not, though, then stop, because the player *must* choose to open the door
 	local node = self.running.path[self.running.cnt]
 	local terrain = node and game.level.map(node.x, node.y, Map.TERRAIN)
-	if self.running.explore == "door" and #self.running.path == self.running.cnt then
-		if self.running.cnt == 1 then
-			-- let's not make assumptions.  Double-check that there is a closed door there and that we open it
-			if terrain.door_opened then
-				local sx, sy = self.x, self.y
-				self:move(node.x, node.y)
-				self:runMoved()
-				-- check if there are enemies behind the open door
-				local ret, msg = self:runCheck()
-				if not ret then
-					self:runStop(msg)
-					return false
-				end
-				terrain = game.level.map(node.x, node.y, Map.TERRAIN)
-				if not terrain.door_opened and sx == self.x and sy == self.y then
-					return self:autoExplore()
-				end
-			end
-		end
-		return false
+
+	-- this is either a "safe" door or a target adjacent door.  Either way, we can open it
+	if terrain and terrain.door_opened then
+		-- we already tried to open the door but failed
+		if self.running.busy and self.running.busy.type == "opening door" then return false end
+
+		self.running.busy = { type = "opening door", do_move = true, no_energy = true }
+		return true
+	end
+	self.running.busy = nil
+
+	-- if we opened the adjacent target door, then continue exploring elsewhere
+	if self.running.explore == "door" and #self.running.path == self.running.cnt and self.running.cnt == 1 and terrain.door_closed then
+		return self:autoExplore()
 	end
 
 	-- if we're at the end of the path and we're searching for unseen tiles, then continue with a new path
diff --git a/game/modules/tome/dialogs/CharacterSheet.lua b/game/modules/tome/dialogs/CharacterSheet.lua
index b393181d3bf2f01bd4954f7e4b5570598cdf3b7f..2315c5b2c4d99673057a8224fcff728e733a9419 100644
--- a/game/modules/tome/dialogs/CharacterSheet.lua
+++ b/game/modules/tome/dialogs/CharacterSheet.lua
@@ -306,9 +306,9 @@ function _M:drawDialog(kind, actor_to_compare)
 		local cur_exp, max_exp = player.exp, player:getExpChart(player.level+1)
 		h = 0
 		w = 0
-		s:drawStringBlended(self.font, "Sex  : "..(player.descriptor.sex or (player.female and "Female" or "Male")), w, h, 0, 200, 255, true) h = h + self.font_h
-		s:drawStringBlended(self.font, "Race : "..(player.descriptor.subrace or player.type:capitalize()), w, h, 0, 200, 255, true) h = h + self.font_h
-		s:drawStringBlended(self.font, "Class: "..(player.descriptor.subclass or player.subtype:capitalize()), w, h, 0, 200, 255, true) h = h + self.font_h
+		s:drawStringBlended(self.font, "Sex  : "..((player.descriptor and player.descriptor.sex) or (player.female and "Female" or "Male")), w, h, 0, 200, 255, true) h = h + self.font_h
+		s:drawStringBlended(self.font, (player.descriptor and "Race : " or "Type : ")..((player.descriptor and player.descriptor.subrace) or player.type:capitalize()), w, h, 0, 200, 255, true) h = h + self.font_h
+		s:drawStringBlended(self.font, (player.descriptor and "Class: " or "Stype: ")..((player.descriptor and player.descriptor.subclass) or player.subtype:capitalize()), w, h, 0, 200, 255, true) h = h + self.font_h
 		s:drawStringBlended(self.font, "Size : "..(player:TextSizeCategory():capitalize()), w, h, 0, 200, 255, true) h = h + self.font_h
 
 		h = h + self.font_h
@@ -375,8 +375,10 @@ function _M:drawDialog(kind, actor_to_compare)
 		text = compare_fields(player, actor_to_compare, function(actor) return actor.combat_physspeed - 1 end, "%.2f%%", "%+.2f%%", 100)
 		self:mouseTooltip(self.TOOLTIP_SPEED_ATTACK,   s:drawColorStringBlended(self.font, ("Attack speed  : #00ff00#%s"):format(text), w, h, 255, 255, 255, true)) h = h + self.font_h
 		h = h + self.font_h
-		text = compare_fields(player, actor_to_compare, function(actor) return #actor.died_times end, "%3d", "%+.0f")
-		self:mouseTooltip(self.TOOLTIP_LIVES,       s:drawColorStringBlended(self.font, ("Times died     : #00ff00#%s"):format(text), w, h, 255, 255, 255, true)) h = h + self.font_h
+		if player.died_times then
+			text = compare_fields(player, actor_to_compare, function(actor) return #actor.died_times end, "%3d", "%+.0f")
+			self:mouseTooltip(self.TOOLTIP_LIVES,       s:drawColorStringBlended(self.font, ("Times died     : #00ff00#%s"):format(text), w, h, 255, 255, 255, true)) h = h + self.font_h
+		end
 		if player.easy_mode_lifes then
 			text = compare_fields(player, actor_to_compare, "easy_mode_lifes", "%3d", "%+.0f")
 			self:mouseTooltip(self.TOOLTIP_LIVES, s:drawColorStringBlended(self.font,   ("Lives left     : #00ff00#%s"):format(text), w, h, 255, 255, 255, true)) h = h + self.font_h
@@ -670,7 +672,7 @@ function _M:drawDialog(kind, actor_to_compare)
 				end
 			end
 		end
-		
+
 		local inc_damage_actor_types = {}
 		if player.inc_damage_actor_type then
 			for i, t in pairs(player.inc_damage_actor_type) do
@@ -871,13 +873,13 @@ function _M:dump()
 	nl("  [Tome 4.00 @ www.te4.org Character Dump]")
 	nl()
 
-	nnl(("%-32s"):format(makelabel("Sex", player.descriptor.sex or (player.female and "Female" or "Male"))))
+	nnl(("%-32s"):format(makelabel("Sex", (player.descriptor and player.descriptor.sex) or (player.female and "Female" or "Male"))))
 	nl(("STR:  %d"):format(player:getStr()))
 
-	nnl(("%-32s"):format(makelabel("Race", player.descriptor.subrace or player.type:capitalize())))
+	nnl(("%-32s"):format(makelabel("Race", (player.descriptor and player.descriptor.subrace) or player.type:capitalize())))
 	nl(("DEX:  %d"):format(player:getDex()))
 
-	nnl(("%-32s"):format(makelabel("Class", player.descriptor.subclass or player.subtype:capitalize())))
+	nnl(("%-32s"):format(makelabel("Class", (player.descriptor and player.descriptor.subclass) or player.subtype:capitalize())))
 	nl(("MAG:  %d"):format(player:getMag()))
 
 	nnl(("%-32s"):format(makelabel("Level", ("%d"):format(player.level))))
@@ -934,8 +936,8 @@ function _M:dump()
 	else
 		 nnl(("%-32s"):format(" "))
 	end
-	nl(makelabel("Difficulty", player.descriptor.difficulty or "???"))
-	nl(makelabel("Permadeath", player.descriptor.permadeath or "???"))
+	nl(makelabel("Difficulty", (player.descriptor and player.descriptor.difficulty) or "???"))
+	nl(makelabel("Permadeath", (player.descriptor and player.descriptor.permadeath) or "???"))
 
 	nnl(("%-32s"):format(strings[3]))
 	if player:knowTalent(player.T_MANA_POOL) then
diff --git a/game/modules/tome/dialogs/MapMenu.lua b/game/modules/tome/dialogs/MapMenu.lua
index a2db360ca00bbb1052a304949ebad4f1b5b6c36f..ca16498ae37c361205ecd5b97a9b8a75ca70d1f7 100644
--- a/game/modules/tome/dialogs/MapMenu.lua
+++ b/game/modules/tome/dialogs/MapMenu.lua
@@ -72,7 +72,7 @@ function _M:use(item)
 	elseif act == "order" then game.party:giveOrders(item.actor)
 	elseif act == "change_level" then game.key:triggerVirtual("CHANGE_LEVEL")
 	elseif act == "pickup" then game.key:triggerVirtual("PICKUP_FLOOR")
-	elseif act == "character_sheet" then game.key:triggerVirtual("SHOW_CHARACTER_SHEET")
+	elseif act == "character_sheet" then game:registerDialog(require("mod.dialogs.CharacterSheet").new(item.actor))
 	elseif act == "quests" then game.key:triggerVirtual("SHOW_QUESTS")
 	elseif act == "levelup" then game.key:triggerVirtual("LEVELUP")
 	elseif act == "inventory" then game.key:triggerVirtual("SHOW_INVENTORY")
@@ -118,8 +118,8 @@ function _M:generateList()
 	if a and not self.on_player and game.party:canOrder(a, false) then list[#list+1] = {name="Give order", action="order", color=colors.simple(colors.TEAL), actor=a} end
 	if self.on_player then list[#list+1] = {name="Rest a while", action="rest", color=colors.simple(colors.ANTIQUE_WHITE)} end
 	if self.on_player then list[#list+1] = {name="Inventory", action="inventory", color=colors.simple(colors.ANTIQUE_WHITE)} end
-	if self.on_player then list[#list+1] = {name="Character Sheet", action="character_sheet", color=colors.simple(colors.ANTIQUE_WHITE)} end
 	if self.on_player then list[#list+1] = {name="Quest Log", action="quests", color=colors.simple(colors.ANTIQUE_WHITE)} end
+	if a then list[#list+1] = {name="Inspect Creature", action="character_sheet", color=colors.simple(colors.ANTIQUE_WHITE), actor=a} end
 	if not self.on_player and a and profile.auth and profile.hash_valid then list[#list+1] = {name="Link creature in chat", action="chat-link"} end
 	if self.on_player and (player.unused_stats > 0 or player.unused_talents > 0 or player.unused_generics > 0 or player.unused_talents_types > 0) then list[#list+1] = {name="Levelup!", action="levelup", color=colors.simple(colors.YELLOW)} end