diff --git a/game/modules/tome/class/Game.lua b/game/modules/tome/class/Game.lua
index 76840edbd60995cf0c65d5caf2c1bfdb7bb7a177..f79da91c696b6c4559b7bd276184b3caccb02220 100644
--- a/game/modules/tome/class/Game.lua
+++ b/game/modules/tome/class/Game.lua
@@ -918,7 +918,7 @@ end
 
 function _M:changeLevelReal(lev, zone, params)
 	local oz, ol = self.zone, self.level
-	
+
 	-- Unlock first!
 	if not params.temporary_zone_shift_back and self.level and self.level.temp_shift_zone then
 		self:changeLevelReal(1, "useless", {temporary_zone_shift_back=true})
@@ -1101,7 +1101,7 @@ function _M:changeLevelReal(lev, zone, params)
 					list[#list+1] = {i, j}
 				end
 			end end
-			if #list > 0 then x, y = unpack(rng.table(list)) end
+			if #list > 0 then x, y = unpack((rng.table(list))) end
 		elseif params.auto_level_stair then
 			-- Dirty but quick
 			local list = {}
@@ -1111,7 +1111,7 @@ function _M:changeLevelReal(lev, zone, params)
 					list[#list+1] = {i, j}
 				end
 			end end
-			if #list > 0 then x, y = unpack(rng.table(list)) end
+			if #list > 0 then x, y = unpack((rng.table(list))) end
 		end
 
 		-- if self.level.exited then -- use the last location, if defined
diff --git a/game/modules/tome/class/GameState.lua b/game/modules/tome/class/GameState.lua
index f67934d1b2c98f5e3d875cad0fdf5e4344c35ed3..3625c01046a57fe0f91e836eaa97398cf28bf3a1 100644
--- a/game/modules/tome/class/GameState.lua
+++ b/game/modules/tome/class/GameState.lua
@@ -2360,8 +2360,13 @@ function _M:locationRevealAround(x, y)
 	end
 end
 
-function _M:doneEvent(id)
-	return self.used_events[id]
+--- Has event been triggered in this game state?
+-- @param id = the event id
+-- @param[optional = number] v increment the event count for id
+-- @return false or the number of times this event has been triggered
+function _M:doneEvent(id, v)
+	if v then self.used_events[id] = (self.used_events[id] or 0) + v end
+	return self.used_events[id] and self.used_events[id] > 0 and self.used_events[id] or false
 end
 
 function _M:canEventGrid(level, x, y)
@@ -2401,6 +2406,10 @@ function _M:findEventGridRadius(level, radius, min)
 	return self:canEventGridRadius(level, x, y, radius, min)
 end
 
+--- Get the file name for an event
+-- @param[string] subdirectory of the base events directory containing the event file
+-- @param[string] name the short name of the event
+-- @return the complete file path for the event file (resolved for addons)
 function _M:eventBaseName(sub, name)
 	local base = "/data"
 	local _, _, addon, rname = name:find("^([^+]+)%+(.+)$")
@@ -2411,8 +2420,30 @@ function _M:eventBaseName(sub, name)
 	return base.."/general/events/"..sub..name..".lua"
 end
 
+--- Process the zone.events table, managing spawning of events on each level
+-- 	If zone.events_by_level is true, events will be assigned to each level as it's generated
+--		otherwise events will be preassigned to each level (stored in zone.assigned_events)
+--		If zone.events.one_per_level is true, only one major event will be preassigned to each level
+--			(Some events may not spawn if there are not enough levels.)
+-- Each event in the events list can have the following fields:
+-- name: short name of the event (resolved with game.state:eventBaseName(dir, name) to get the full file path
+-- group: if name is not set, load all events present in the associated group file in the groups subdirectory
+-- 		percent_factor: percent multiplier for the loaded group events only
+--		forbid, level_range will be merged
+-- minor: flag event as a minor event that can spawn multiple times on the level
+-- percent: (required) % chance for the event to be assigned to a given level
+-- always: set true to force 100% spawn chance
+-- level_range: table {low, high} containing the range of levels the event is allowed to spawn on
+-- forbid: table of levels the event cannot spawn on
+-- special: a function(lev) that, if present, must return true to allow the event to spawn on level lev
+-- minor event fields:
+-- 	max_repeat: attempt to spawn the event extra times (% chance halved after each repeat)
+-- major event fields:
+-- 	unique: set true to allow only one instance of the event for this game state
+-- @return a function(level) to place events on the level (loaded from the event file)
+--		this function loads and executes all of the required events files to modify the map, etc.
 function _M:startEvents()
-	if not game.zone.events then print("No zone events loaded") return end
+	if not game.zone.events then print("[STARTEVENTS] No zone events loaded") return end
 
 	if not game.zone.assigned_events then
 		local levels = {}
@@ -2422,58 +2453,76 @@ function _M:startEvents()
 			for i = 1, game.zone.max_level do levels[i] = {} end
 		end
 
-		-- Generate the events list for this zone, eventually loading from group files
+		-- Generate the events list for this zone, possibly loading from group files
 		local evts, mevts = {}, {}
 		for i, e in ipairs(game.zone.events) do
-			if e.name then if e.minor then mevts[#mevts+1] = e else evts[#evts+1] = e end
-			elseif e.group then
+			if e.name then -- add a single event to the events list
+				if e.minor then	mevts[#mevts+1] = e else evts[#evts+1] = e end
+			elseif e.group then -- load events from a group file and add them to the events list
+				--	print("[STARTEVENTS] loading events group", e.group)
 				local f, err = loadfile(self:eventBaseName("groups/", e.group))
 				if not f then error(err) end
 				setfenv(f, setmetatable({level=game.level, zone=game.zone}, {__index=_G}))
 				local list = f()
 				for j, ee in ipairs(list) do
-					if e.percent_factor and ee.percent then ee.percent = math.floor(ee.percent * e.percent_factor) end
-					if e.forbid then ee.forbid = table.append(ee.forbid or {}, e.forbid) end
-					if ee.name then if ee.minor then mevts[#mevts+1] = ee else evts[#evts+1] = ee end end
+				--	print("[STARTEVENTS]\t\tAdding Group Event:", j, tostring(ee.name))
+					if ee.name then
+						if e.percent_factor and ee.percent then ee.percent = math.floor(ee.percent * e.percent_factor) end
+						if e.forbid then ee.forbid = table.append(ee.forbid or {}, e.forbid) end
+						if e.level_range then
+							if ee.level_range then
+								ee.level_range = {math.max(e.level_range[1] or 1, ee.level_range[1] or 1),
+									math.min(e.level_range[2] or math.huge, ee.level_range[2] or math.huge)}
+							else ee.level_range = e.level_range
+							end
+						end
+						if ee.minor then mevts[#mevts+1] = ee else evts[#evts+1] = ee end
+					end
 				end
 			end
 		end
 
 		-- Randomize the order they are checked as
+		print("[STARTEVENTS] Zone compiled events list: one_per_level=", game.zone.events.one_per_level)
 		table.shuffle(evts)
-		print("[STARTEVENTS] Zone events list:")
 		table.print(evts)
 		table.shuffle(mevts)
 		table.print(mevts)
 		for i, e in ipairs(evts) do
-			-- If we allow it, try to find a level to host it
-			if (e.always or rng.percent(e.percent) or (e.special and e.special() == true)) and (not e.unique or not self:doneEvent(e.name)) then
+			-- If allowed, find a level to host the (major) event
+			if (e.always or rng.percent(e.percent)) and (not e.unique or not self:doneEvent(e.name)) then
 				local lev = nil
 				local forbid = e.forbid or {}
 				forbid = table.reverse(forbid)
 				if game.zone.events_by_level then
 					lev = game.level.level
+					if forbid[lev] then lev = nil
+					elseif e.level_range and (lev < (e.level_range[1] or 1) or lev > (e.level_range[2] or game.zone.max_level)) then lev = nil end
 				else
-					if game.zone.events.one_per_level then
+					local start, stop = 1, game.zone.max_level
+					if e.level_range then start, stop = e.level_range[1] or start, e.level_range[2] or stop end
+					if game.zone.events.one_per_level then -- find a random level with no assigned event
 						local list = {}
-						for i = 1, #levels do if #levels[i] == 0 and not forbid[i] then list[#list+1] = i end end
+						for i = start, stop do
+							if #levels[i] == 0 and not forbid[i] and (not e.special or e.special(i)) then
+								list[#list+1] = i
+							end
+						end
 						if #list > 0 then
 							lev = rng.table(list)
 						end
-					else
-						if forbid then
-							local t = table.genrange(1, game.zone.max_level, true)
-							t = table.minus_keys(t, forbid)
-							lev = rng.table(table.keys(t))
-						else
-							lev = rng.range(1, game.zone.max_level)
-						end
+					else -- pick an allowed level at random to assign the event to
+						local t = table.genrange(start, stop, true)
+						if e.special then table.foreach(t, function(i, v) t[i] = e.special(i) and t[i] or nil end) end
+						t = table.minus_keys(t, forbid)
+						lev = rng.table(table.keys(t))
 					end
 				end
 
 				if lev then
 					lev = levels[lev]
 					lev[#lev+1] = e.name
+					self:doneEvent(e.name, 1) -- mark as done when assigned
 				end
 			end
 		end
@@ -2482,23 +2531,27 @@ function _M:startEvents()
 			forbid = table.reverse(forbid)
 
 			local start, stop = 1, game.zone.max_level
-			if game.zone.events_by_level then start, stop = game.level.level, game.level.level end
-			for lev = start, stop do
-				if rng.percent(e.percent) and not forbid[lev] then
-					local lev = levels[lev]
-					lev[#lev+1] = e.name
-
-					if e.max_repeat then
+			if game.zone.events_by_level then
+				start, stop = game.level.level, game.level.level
+			end
+			if e.level_range then
+				start, stop = math.max(start, e.level_range[1] or start), math.min(stop, e.level_range[2] or stop)
+			end
+			for lv = start, stop do
+				if (e.always or rng.percent(e.percent)) and not forbid[lv] and (not e.special or e.special(lv)) then
+					local lev = levels[lv]
+					lev[#lev+1] = e.name self:doneEvent(e.name, 1) -- mark as done when assigned
+					if e.max_repeat then -- try to repeat the event with diminishing probability
 						local nb = 1
-						local p = e.percent
+						local p = e.percent or 100
 						while nb <= e.max_repeat do
-							if rng.percent(p) then
-								lev[#lev+1] = e.name
+							if e.always or rng.percent(p) and (not e.special or e.special(lv)) then
+								lev[#lev+1] = e.name self:doneEvent(e.name, 1) -- mark as done when assigned
 								nb = nb + 1
+								p = p/2
 							else
 								break
 							end
-							p = p / 2
 						end
 					end
 				end
@@ -2508,6 +2561,7 @@ function _M:startEvents()
 		game.zone.assigned_events = levels
 	end
 
+	-- return a wrapper function to load and run all assigned events files
 	return function()
 		print("[STARTEVENTS] Assigned events list:")
 		table.print(game.zone.assigned_events)
@@ -2515,8 +2569,9 @@ function _M:startEvents()
 		for i, e in ipairs(game.zone.assigned_events[game.level.level] or {}) do
 			local f, err = loadfile(self:eventBaseName("", e))
 			if not f then error(err) end
-			setfenv(f, setmetatable({level=game.level, zone=game.zone, event_id=e.name, Map=Map}, {__index=_G}))
-			f()
+			setfenv(f, setmetatable({level=game.level, zone=game.zone, event_id=e, Map=Map}, {__index=_G}))
+			self:doneEvent(e, -1) -- unmark as done (for event code)
+			if f() then self:doneEvent(e, 1) end -- remark as done if event completed
 		end
 		game.zone.assigned_events[game.level.level] = {}
 		if game.zone.events_by_level then game.zone.assigned_events = nil end
diff --git a/game/modules/tome/class/generator/map/VaultLevel.lua b/game/modules/tome/class/generator/map/VaultLevel.lua
index edde9883cf2cfbcbfcf6666e4e775f72ae110782..d0b7ae337461975005a84b8e0994ceaee6c97179 100644
--- a/game/modules/tome/class/generator/map/VaultLevel.lua
+++ b/game/modules/tome/class/generator/map/VaultLevel.lua
@@ -57,7 +57,7 @@ function _M:generate(lev, old_lev)
 		local rm
 		for i = 0, self.map.w - 1 do for j = 0, self.map.h - 1 do
 			rm = self.map.room_map[i][j]
-			if not rm.room then
+			if not (rm.room or rm.special or rm.can_open) then
 				local g
 				if self.level.data.subvaults_surroundings then g = self:resolve(self.level.data.subvaults_surroundings, nil, true)
 				else g = self:resolve("subvault_wall") end
@@ -81,12 +81,13 @@ function _M:generate(lev, old_lev)
 			sx, sy = e.sx, e.sy
 			ex, ey = e.x, e.y
 			if not (sx and sy) then -- vault connection point: connect to a (nearest) non-vault grid
+				print("[VaultLevel] generating level start point from vault connection at", ex, ey)
+				-- start at center of room and pick a random direction
 				sx, sy = math.floor(rx+(room.w-1)/2), math.floor(ry+(room.h-1)/2)
 				local dir, xd, yd = util.getDir(ex, ey, sx+rng.normal(0, 1), sy+rng.normal(0, 1))
 				if dir == 5 then dir = rng.table(util.primaryDirs()) end
 				local rm
-					
-				local steps = (self.map.w + self.map.h)/4
+				local steps = math.max(room.w, room.h) + 2*(room.border or 0) + 1
 				repeat
 					steps = steps - 1
 					rm = self.map.room_map[sx][sy]
diff --git a/game/modules/tome/data/general/events/conclave-vault.lua b/game/modules/tome/data/general/events/conclave-vault.lua
index 7c687facfc281acaff79ab55a74d02a4760d2a65..51d566542417b7032c93e9c4478fea466f0254a4 100644
--- a/game/modules/tome/data/general/events/conclave-vault.lua
+++ b/game/modules/tome/data/general/events/conclave-vault.lua
@@ -21,6 +21,7 @@
 local x, y = game.state:findEventGrid(level)
 if not x then return false end
 
+print("[EVENT] Placing event conclave-vault at", x, y)
 local Talents = require("engine.interface.ActorTalents")
 
 local skeletons = mod.class.NPC:loadList("/data/general/npcs/skeleton.lua")
diff --git a/game/modules/tome/data/general/events/cultists.lua b/game/modules/tome/data/general/events/cultists.lua
index 9ca89541d9305a8407111f249df5b378b2ca940e..ccc70daa1e69af69c5e1a992f0812b9555cbf044 100644
--- a/game/modules/tome/data/general/events/cultists.lua
+++ b/game/modules/tome/data/general/events/cultists.lua
@@ -17,7 +17,7 @@
 -- Nicolas Casalini "DarkGod"
 -- darkgod@te4.org
 
--- Unique
+-- Unique throughout the game
 if game.state:doneEvent(event_id) then return end
 
 local list = {}
diff --git a/game/modules/tome/data/general/events/damp-cave.lua b/game/modules/tome/data/general/events/damp-cave.lua
index 2a4e1420a34dbeb504a8708f748855f5816dee52..1a98048b483cc3c9d7e90187d9d575e5469859da 100644
--- a/game/modules/tome/data/general/events/damp-cave.lua
+++ b/game/modules/tome/data/general/events/damp-cave.lua
@@ -23,15 +23,21 @@ if not x then return false end
 
 local id = "damp-cave-"..game.turn
 
+print("[EVENT] Placing event", id, "at", x, y)
+
 local changer = function(id)
 	local npcs = mod.class.NPC:loadList{"/data/general/npcs/thieve.lua"}
 	local objects = mod.class.Object:loadList("/data/general/objects/objects.lua")
 	local terrains = mod.class.Grid:loadList("/data/general/grids/cave.lua")
 	terrains.CAVE_LADDER_UP_WILDERNESS.change_level_shift_back = true
 	terrains.CAVE_LADDER_UP_WILDERNESS.change_zone_auto_stairs = true
+	terrains.CAVE_LADDER_UP_WILDERNESS.name = "ladder back to "..game.zone.name
+	terrains.CAVE_LADDER_UP_WILDERNESS.change_zone = game.zone.short_name
 	local zone = mod.class.Zone.new(id, {
 		name = "Damp Cave",
-		level_range = {game.zone:level_adjust_level(game.level, game.zone, "actor"), game.zone:level_adjust_level(game.level, game.zone, "actor")},
+		level_range = game.zone.actor_adjust_level and {math.floor(game.zone:actor_adjust_level(game.level, game.player)*1.05),
+			math.ceil(game.zone:actor_adjust_level(game.level, game.player)*1.15)} or {game.zone.base_level, game.zone.base_level}, -- 5-15% higher actor levels
+		__applied_difficulty = true, -- Difficulty already applied to parent zone
 		level_scheme = "player",
 		max_level = 1,
 		actor_adjust_level = function(zone, level, e) return zone.base_level + e:getRankLevelAdjust() + level.level-1 + rng.range(-1,2) end,
@@ -39,8 +45,10 @@ local changer = function(id)
 		ambient_music = "Swashing the buck.ogg",
 		reload_lists = false,
 		persistent = "zone",
-		min_material_level = game.zone.min_material_level,
-		max_material_level = game.zone.max_material_level,
+		
+		no_worldport = game.zone.no_worldport,
+		min_material_level = util.getval(game.zone.min_material_level),
+		max_material_level = util.getval(game.zone.max_material_level),
 		generator =  {
 			map = {
 				class = "engine.generator.map.Cavern",
@@ -66,7 +74,6 @@ local changer = function(id)
 				nb_trap = {6, 9},
 			},
 		},
---		levels = { [1] = { generator = { map = { up = "CAVEFLOOR", }, }, }, },
 		npc_list = npcs,
 		grid_list = terrains,
 		object_list = objects,
@@ -77,6 +84,7 @@ end
 
 local g = game.level.map(x, y, engine.Map.TERRAIN):cloneFull()
 g.name = "damp cave"
+g.always_remember = true
 g.display='>' g.color_r=0 g.color_g=0 g.color_b=255 g.notice = true
 g.change_level=1 g.change_zone=id g.glow=true
 g:removeAllMOs()
@@ -84,6 +92,7 @@ if engine.Map.tiles.nicer_tiles then
 	g.add_displays = g.add_displays or {}
 	g.add_displays[#g.add_displays+1] = mod.class.Grid.new{image="terrain/crystal_ladder_down.png", z=5}
 end
+g.nice_tiler = nil
 g:altered()
 g:initGlow()
 g.real_change = changer
@@ -91,8 +100,9 @@ g.change_level_check = function(self)
 	game:changeLevel(1, self.real_change(self.change_zone), {temporary_zone_shift=true, direct_switch=true})
 	self.change_level_check = nil
 	self.real_change = nil
+	self.special_minimap = colors.VIOLET
 	return true
 end
 game.zone:addEntity(game.level, g, "terrain", x, y)
 
-return true
+return x, y
diff --git a/game/modules/tome/data/general/events/drake-cave.lua b/game/modules/tome/data/general/events/drake-cave.lua
index 7268509e7ab4877a6b729754f5af97f5907e6665..83fa21f69201c8b56b811422f38d58ed9a23f5ad 100644
--- a/game/modules/tome/data/general/events/drake-cave.lua
+++ b/game/modules/tome/data/general/events/drake-cave.lua
@@ -18,27 +18,28 @@
 -- darkgod@te4.org
 
 -- Find a random spot
-local x, y = rng.range(1, level.map.w - 2), rng.range(1, level.map.h - 2)
-local tries = 0
-while not game.state:canEventGrid(level, x, y) and tries < 100 do
-	x, y = rng.range(1, level.map.w - 2), rng.range(1, level.map.h - 2)
-	tries = tries + 1
-end
-if tries >= 100 then return false end
+local x, y = game.state:findEventGrid(level)
+if not x then return false end
 
 local kind = rng.table{"fire", "fire", "cold", "cold", "storm", "storm", "multihued"}
 
 local id = kind.."-dragon-cave-"..game.turn
 
+print("[EVENT] Placing event", id, "at", x, y)
+
 local changer = function(id, kind)
 	local npcs = mod.class.NPC:loadList{"/data/general/npcs/"..kind.."-drake.lua"}
 	local objects = mod.class.Object:loadList("/data/general/objects/objects.lua")
 	local terrains = mod.class.Grid:loadList("/data/general/grids/cave.lua")
 	terrains.CAVE_LADDER_UP_WILDERNESS.change_level_shift_back = true
 	terrains.CAVE_LADDER_UP_WILDERNESS.change_zone_auto_stairs = true
+	terrains.CAVE_LADDER_UP_WILDERNESS.name = "ladder back to "..game.zone.name
+	terrains.CAVE_LADDER_UP_WILDERNESS.change_zone = game.zone.short_name
 	local zone = mod.class.Zone.new(id, {
 		name = "Intimidating Cave",
-		level_range = {game.zone:level_adjust_level(game.level, game.zone, "actor"), game.zone:level_adjust_level(game.level, game.zone, "actor")},
+		level_range = game.zone.actor_adjust_level and {math.floor(game.zone:actor_adjust_level(game.level, game.player)*1.05),
+			math.ceil(game.zone:actor_adjust_level(game.level, game.player)*1.15)} or {game.zone.base_level, game.zone.base_level}, -- 5-15% higher levels
+		__applied_difficulty = true, --Difficulty already applied to parent zone
 		level_scheme = "player",
 		max_level = 1,
 		actor_adjust_level = function(zone, level, e) return zone.base_level + e:getRankLevelAdjust() + level.level-1 + rng.range(-1,2) end,
@@ -46,8 +47,10 @@ local changer = function(id, kind)
 		ambient_music = "Swashing the buck.ogg",
 		reload_lists = false,
 		persistent = "zone",
-		min_material_level = game.zone.min_material_level,
-		max_material_level = game.zone.max_material_level,
+		
+		no_worldport = game.zone.no_worldport,
+		min_material_level = util.getval(game.zone.min_material_level),
+		max_material_level = util.getval(game.zone.max_material_level),
 		generator =  {
 			map = {
 				class = "engine.generator.map.Cavern",
@@ -61,7 +64,10 @@ local changer = function(id, kind)
 			actor = {
 				class = "mod.class.generator.actor.Random",
 				nb_npc = {25, 25},
-				guardian = {special=function(e) return e.rank and e.rank >= 2 end, random_elite={life_rating=function(v) return v * 1.5 + 4 end, nb_rares=3}},
+				guardian = {special=function(e) return e.rank and e.rank >= 2 end, random_elite={life_rating=function(v) return v * 1.5 + 4 end,
+				nb_rares=(rng.percent(resolvers.current_level-50) and 4 or 3),
+				nb_classes=(rng.percent(resolvers.current_level-50) and 2 or 1)}
+				},
 			},
 			object = {
 				class = "engine.generator.object.Random",
@@ -73,7 +79,6 @@ local changer = function(id, kind)
 				nb_trap = {6, 9},
 			},
 		},
---		levels = { [1] = { generator = { map = { up = "CAVEFLOOR", }, }, }, },
 		npc_list = npcs,
 		grid_list = terrains,
 		object_list = objects,
@@ -85,12 +90,14 @@ end
 local g = game.level.map(x, y, engine.Map.TERRAIN):cloneFull()
 g.name = "intimidating cave"
 g.display='>' g.color_r=0 g.color_g=0 g.color_b=255 g.notice = true
+g.always_remember = true
 g.change_level=1 g.change_zone=id g.glow=true
 g:removeAllMOs()
 if engine.Map.tiles.nicer_tiles then
 	g.add_displays = g.add_displays or {}
 	g.add_displays[#g.add_displays+1] = mod.class.Grid.new{image="terrain/crystal_ladder_down.png", z=5}
 end
+g.nice_tiler = nil
 g:altered()
 g:initGlow()
 g.dragon_kind = kind
@@ -99,6 +106,7 @@ g.change_level_check = function(self)
 	game:changeLevel(1, self.real_change(self.change_zone, self.dragon_kind), {temporary_zone_shift=true, direct_switch=true})
 	self.change_level_check = nil
 	self.real_change = nil
+	self.special_minimap = colors.VIOLET
 	return true
 end
 game.zone:addEntity(game.level, g, "terrain", x, y)
@@ -113,4 +121,4 @@ if m then
 	game.zone:addEntity(game.level, m, "actor", i, j)
 end
 
-return true
+return x, y
diff --git a/game/modules/tome/data/general/events/fearscape-portal.lua b/game/modules/tome/data/general/events/fearscape-portal.lua
index 7e3b944c354ed43d294a722cc1c4df6a62c03288..b67792e30ac6d9581e3a02229af7fc3f20c4e044 100644
--- a/game/modules/tome/data/general/events/fearscape-portal.lua
+++ b/game/modules/tome/data/general/events/fearscape-portal.lua
@@ -18,16 +18,13 @@
 -- darkgod@te4.org
 
 -- Find a random spot
-local x, y = rng.range(1, level.map.w - 2), rng.range(1, level.map.h - 2)
-local tries = 0
-while not game.state:canEventGrid(level, x, y) and tries < 100 do
-	x, y = rng.range(1, level.map.w - 2), rng.range(1, level.map.h - 2)
-	tries = tries + 1
-end
-if tries >= 100 then return false end
+local x, y = game.state:findEventGrid(level)
+if not x then return false end
 
 local id = "fearscape-invasion-"..game.turn
 
+print("[EVENT] Placing event", id, "at", x, y)
+
 local changer = function(id)
 	local npcs = mod.class.NPC:loadList{"/data/general/npcs/minor-demon.lua", "/data/general/npcs/major-demon.lua"}
 	local objects = mod.class.Object:loadList("/data/general/objects/objects.lua")
@@ -37,16 +34,24 @@ local changer = function(id)
 	terrains.PORTAL_BACK = mod.class.Grid.new{
 		type = "floor", subtype = "floor",
 		display = "&", color = colors.BLUE,
-		name = "portal",
+		name = "portal back to "..game.zone.name,
 		image = "terrain/red_floating_rocks05_01.png",
 		add_displays = { mod.class.Grid.new{image="terrain/demon_portal3.png"} },
-		change_level = 1, change_zone = "wilderness",
+		change_level = 1,
+		change_zone = game.zone.short_name,
 		change_level_shift_back = true,
 		change_zone_auto_stairs = true,
+		change_level_check = function(self)
+			game.log("#VIOLET# You escape the Fearscape!")
+			-- May delete old zone file here?
+			return
+		end
 	}
 	local zone = mod.class.Zone.new(id, {
 		name = "orbital fearscape platform",
-		level_range = {game.zone:level_adjust_level(game.level, game.zone, "actor"), game.zone:level_adjust_level(game.level, game.zone, "actor")},
+		level_range = game.zone.actor_adjust_level and {math.floor(game.zone:actor_adjust_level(game.level, game.player)*1.05),
+			math.ceil(game.zone:actor_adjust_level(game.level, game.player)*1.15)} or {game.zone.base_level, game.zone.base_level}, -- 5-15% higher levels
+		__applied_difficulty = true, -- Difficulty already applied to parent zone
 		level_scheme = "player",
 		max_level = 1,
 		actor_adjust_level = function(zone, level, e) return zone.base_level + e:getRankLevelAdjust() + level.level-1 + rng.range(-1,2) end,
@@ -55,8 +60,10 @@ local changer = function(id)
 		reload_lists = false,
 		projectile_speed_mod = 0.3,
 		persistent = "zone",
-		min_material_level = game.zone.min_material_level,
-		max_material_level = game.zone.max_material_level,
+		
+		no_worldport = game.zone.no_worldport,
+		min_material_level = util.getval(game.zone.min_material_level),
+		max_material_level = util.getval(game.zone.max_material_level),
 		effects = {"EFF_ZONE_AURA_FEARSCAPE"},
 		generator =  {
 			map = {
@@ -71,7 +78,10 @@ local changer = function(id)
 			actor = {
 				class = "mod.class.generator.actor.Random",
 				nb_npc = {12, 12},
-				guardian = {random_elite={life_rating=function(v) return v * 1.5 + 4 end, nb_rares=4, name_scheme="#rng# the Invader", on_die=function(self) world:gainAchievement("EVENT_FEARSCAPE", game:getPlayer(true)) end}},
+				guardian = {random_elite={life_rating=function(v) return v * 1.5 + 4 end, name_scheme="#rng# the Invader", on_die=function(self) world:gainAchievement("EVENT_FEARSCAPE", game:getPlayer(true)) end,
+				nb_rares=(rng.percent(resolvers.current_level-50) and 5 or 4),
+				nb_classes=(rng.percent(resolvers.current_level-50) and 2 or 1)
+				}}
 			},
 			object = {
 				class = "engine.generator.object.Random",
@@ -96,7 +106,6 @@ local changer = function(id)
 			game.zone.world_sphere_rot = (game.zone.world_sphere_rot or 0)
 			game.zone.cloud_sphere_rot = (game.zone.world_cloud_rot or 0)
 		end,
-
 		background = function(level, x, y, nb_keyframes)
 			local Map = require "engine.Map"
 			local parx, pary = level.map.mx / (level.map.w - Map.viewport.mwidth), level.map.my / (level.map.h - Map.viewport.mheight)
@@ -132,8 +141,6 @@ local changer = function(id)
 			core.display.glMatrix(false)
 			core.display.glDepthTest(false)
 		end,
-
---		levels = { [1] = { generator = { map = { up = "CAVEFLOOR", }, }, }, },
 		npc_list = npcs,
 		grid_list = terrains,
 		object_list = objects,
@@ -144,7 +151,10 @@ end
 
 local g = game.level.map(x, y, engine.Map.TERRAIN):cloneFull()
 g.name = "fearscape invasion portal"
+g.always_remember = true
+g.show_tooltip = true
 g.display='&' g.color_r=0 g.color_g=0 g.color_b=255 g.notice = true
+g.special_minimap = colors.VIOLET
 g.change_level=1 g.change_zone=id g.glow=true
 g:removeAllMOs()
 if engine.Map.tiles.nicer_tiles then
@@ -152,35 +162,43 @@ if engine.Map.tiles.nicer_tiles then
 	g.add_displays[#g.add_displays+1] = mod.class.Grid.new{image="terrain/demon_portal3.png"}
 end
 g.grow = nil g.dig = nil
+g.nice_tiler = nil
 g:altered()
 g:initGlow()
 g.special = true
 g.real_change = changer
+g.break_portal = function(self)
+	self.broken = true
+	game.log("#VIOLET#The portal is broken!")
+	self.name = "broken fearscape invasion portal"
+	self.change_level = nil
+	self.autoexplore_ignore = true
+	self.show_tooltip = false
+end
 g.change_level_check = function(self)
+	self:break_portal()
 	game:changeLevel(1, self.real_change(self.change_zone), {temporary_zone_shift=true, direct_switch=true})
 	game.player:attr("planetary_orbit", 1)
-	self.change_level_check = nil
-	self.real_change = nil
 	return true
 end
-g.on_move = function(self, x, y, who)
+g.on_move = function(self, x, y, who, act, couldpass)
 	if not who or not who.player then return false end
 	if self.broken then
 		game.log("#VIOLET#The portal is already broken!")
 		return false
 	end
 
-	require("engine.ui.Dialog"):yesnoPopup("Fearscape Portal", "Do you wish to enter the portal or just destroy it?", function(ret)
-		game.log("#VIOLET#The portal is broken!")
+	require("engine.ui.Dialog"):yesnoPopup("Fearscape Portal", "Do you wish to enter the portal, destroy it, or ignore it (press escape)?", function(ret)
+		if ret == "Quit" then
+			game.log("#VIOLET#Ignoring the portal...")
+			return
+		end
 		if not ret then
 			self:change_level_check()
+		else self:break_portal()
 		end
-		self.broken = true
-		self.name = "broken "..self.name
-		self.change_level = nil
-		self.autoexplore_ignore = true
-	end, "Destroy", "Enter")
-
+	end, "Destroy", "Enter", false, "Quit")
+	
 	return false
 end
 
@@ -189,27 +207,35 @@ game.zone:addEntity(game.level, g, "terrain", x, y)
 local respawn = function(self)
 	local portal = game.level.map(self.fearscape_portal_x, self.fearscape_portal_y, engine.Map.TERRAIN)
 	if not portal or portal.broken then return end
-	local i, j = util.findFreeGrid(self.fearscape_portal_x, self.fearscape_portal_y+1, 10, true, {[engine.Map.ACTOR]=true})
-	if not i then return end
 
 	local npcs = mod.class.NPC:loadList{"/data/general/npcs/major-demon.lua"}
 	local m = game.zone:makeEntity(game.level, "actor", {base_list=npcs}, nil, true)
 	if not m then return end
 
-	m.fearscape_portal_x = self.fearscape_portal_x
-	m.fearscape_portal_y = self.fearscape_portal_y
-	m.fearscape_respawn = self.fearscape_respawn
-	m.exp_worth = 0
-	m.no_drops = true
-	m.faction = "fearscape"
-	m.on_die = function(self) self:fearscape_respawn() end
-	game.zone:addEntity(game.level, m, "actor", i, j)
-	game.logSeen(m, "#VIOLET#A demon steps out of the portal!")
+	local adjacent = util.adjacentCoords(self.fearscape_portal_x, self.fearscape_portal_y)
+	adjacent[5] = {self.fearscape_portal_x, self.fearscape_portal_y}
+
+	repeat
+		local grid = rng.tableRemove(adjacent)
+		if m:canMove(grid[1], grid[2]) then
+			m.fearscape_portal_x = self.fearscape_portal_x
+			m.fearscape_portal_y = self.fearscape_portal_y
+			m.fearscape_respawn = self.fearscape_respawn
+			m.exp_worth = 0
+			m.no_drops = true
+			m.ingredient_on_death = nil
+			m.faction = "fearscape"
+			m.on_die = function(self) self:fearscape_respawn() end
+			game.zone:addEntity(game.level, m, "actor", grid[1], grid[2])
+			game.logSeen(m, "#VIOLET#A demon steps out of the %s!", portal.name)
+			break
+		end
+	until #adjacent <= 0
 end
 
--- Spawn two that will keep on being replenished
+-- Spawn two demons that will keep on being replenished
 local base = {fearscape_portal_x=x, fearscape_portal_y=y, fearscape_respawn=respawn}
 respawn(base)
 respawn(base)
 
-return true
+return x, y
diff --git a/game/modules/tome/data/general/events/groups/outdoor-majeyal-gloomy.lua b/game/modules/tome/data/general/events/groups/outdoor-majeyal-gloomy.lua
index 28eb458a3e8c4695d2047a929df2f45a57ef9102..97af7cd6ad39afb0233977790a5674e1552e79c6 100644
--- a/game/modules/tome/data/general/events/groups/outdoor-majeyal-gloomy.lua
+++ b/game/modules/tome/data/general/events/groups/outdoor-majeyal-gloomy.lua
@@ -21,5 +21,5 @@ return {
 	{name="tombstones", percent=10},
 	{name="old-battle-field", percent=5},
 	{name="thunderstorm", percent=7},
-	{name="rat-lich", percent=2},
+	{name="rat-lich", percent=2, unique=true},
 }
diff --git a/game/modules/tome/data/general/events/naga-portal.lua b/game/modules/tome/data/general/events/naga-portal.lua
index f0ec2c55f39494a1fc7e64d1810ff2e749bab21a..cc23c8b59fc953d98c3998a50c495ddf1bd3de45 100644
--- a/game/modules/tome/data/general/events/naga-portal.lua
+++ b/game/modules/tome/data/general/events/naga-portal.lua
@@ -18,16 +18,13 @@
 -- darkgod@te4.org
 
 -- Find a random spot
-local x, y = rng.range(1, level.map.w - 2), rng.range(1, level.map.h - 2)
-local tries = 0
-while not game.state:canEventGrid(level, x, y) and tries < 100 do
-	x, y = rng.range(1, level.map.w - 2), rng.range(1, level.map.h - 2)
-	tries = tries + 1
-end
-if tries >= 100 then return false end
+local x, y = game.state:findEventGrid(level)
+if not x then return false end
 
 local id = "naga-invasion-"..game.turn
 
+print("[EVENT] Placing event", id, "at", x, y)
+
 local changer = function(id)
 	local npcs = mod.class.NPC:loadList{"/data/general/npcs/naga.lua"}
 	local objects = mod.class.Object:loadList("/data/general/objects/objects.lua")
@@ -36,30 +33,35 @@ local changer = function(id)
 		type = "floor", subtype = "underwater",
 		display = "&", color = colors.BLUE,
 		name = "coral invasion portal",
+		name = "portal back to "..game.zone.name,
 		image = "terrain/underwater/subsea_floor_02.png",
 		add_displays = {mod.class.Grid.new{z=18, image="terrain/naga_portal.png", display_h=2, display_y=-1, embed_particles = {
 			{name="naga_portal_smoke", rad=2, args={smoke="particles_images/smoke_whispery_bright"}},
 			{name="naga_portal_smoke", rad=2, args={smoke="particles_images/smoke_heavy_bright"}},
 			{name="naga_portal_smoke", rad=2, args={smoke="particles_images/smoke_dark"}},
 		}}},
-		change_level = 1, change_zone = "wilderness",
+		change_level = 1,
+		change_zone = game.zone.short_name,
 		change_level_shift_back = true,
 		change_zone_auto_stairs = true,
 	}
 	local zone = mod.class.Zone.new(id, {
 		name = "water cavern",
-		level_range = {game.zone:level_adjust_level(game.level, game.zone, "actor"), game.zone:level_adjust_level(game.level, game.zone, "actor")},
+		level_range = game.zone.actor_adjust_level and {math.floor(game.zone:actor_adjust_level(game.level, game.player)*1.05),
+			math.ceil(game.zone:actor_adjust_level(game.level, game.player)*1.15)} or {game.zone.base_level, game.zone.base_level}, -- 5-15% higher levels
+		__applied_difficulty = true, -- Difficulty already applied to parent zone
 		level_scheme = "player",
 		max_level = 1,
 		actor_adjust_level = function(zone, level, e) return zone.base_level + e:getRankLevelAdjust() + level.level-1 + rng.range(-1,2) end,
 		width = 30, height = 30,
 		ambient_music = "Dark Secrets.ogg",
 		reload_lists = false,
+		no_worldport = game.zone.no_worldport,
 		color_shown = {0.5, 1, 0.8, 1},
 		color_obscure = {0.5*0.6, 1*0.6, 0.8*0.6, 0.6},
 		persistent = "zone",
-		min_material_level = game.zone.min_material_level,
-		max_material_level = game.zone.max_material_level,
+		min_material_level = util.getval(game.zone.min_material_level),
+		max_material_level = util.getval(game.zone.max_material_level),
 		effects = {"EFF_ZONE_AURA_UNDERWATER"},
 		generator =  {
 			map = {
@@ -76,7 +78,10 @@ local changer = function(id)
 			actor = {
 				class = "mod.class.generator.actor.Random",
 				nb_npc = {12, 12},
-				guardian = {random_elite={life_rating=function(v) return v * 1.5 + 4 end, nb_rares=4, name_scheme="#rng# the Tidebender", on_die=function(self) world:gainAchievement("EVENT_NAGA", game:getPlayer(true)) end}},
+				guardian = {random_elite={life_rating=function(v) return v * 1.5 + 4 end, name_scheme="#rng# the Tidebender", on_die=function(self) world:gainAchievement("EVENT_NAGA", game:getPlayer(true)) end,
+				nb_rares=(rng.percent(resolvers.current_level-50) and 4 or 3),
+				nb_classes=(rng.percent(resolvers.current_level-50) and 2 or 1)
+				}},
 			},
 			object = {
 				class = "engine.generator.object.Random",
@@ -89,7 +94,6 @@ local changer = function(id)
 			},
 		},
 		post_process = function(level) for uid, e in pairs(level.entities) do e.faction = e.hard_faction or "vargh-republic" end end,
---		levels = { [1] = { generator = { map = { up = "CAVEFLOOR", }, }, }, },
 		npc_list = npcs,
 		grid_list = terrains,
 		object_list = objects,
@@ -100,7 +104,9 @@ end
 
 local g = game.level.map(x, y, engine.Map.TERRAIN):cloneFull()
 g.name = "naga invasion coral portal"
+g.always_remember = true
 g.display='&' g.color_r=0 g.color_g=0 g.color_b=255 g.notice = true
+g.special_minimap = colors.VIOLET
 g.change_level=1 g.change_zone=id g.glow=true
 g:removeAllMOs()
 if engine.Map.tiles.nicer_tiles then
@@ -116,30 +122,37 @@ g.grow = nil g.dig = nil
 g:initGlow()
 g.special = true
 g.real_change = changer
+g.break_portal = function(self)
+	game.log("#VIOLET#The portal is broken!")
+	self.broken = true
+	self.name = "broken naga invasion coral portal"
+	self.change_level = nil
+	self.autoexplore_ignore = true
+	self.show_tooltip = false
+end
 g.change_level_check = function(self)
+	self:break_portal()
 	game:changeLevel(1, self.real_change(self.change_zone), {temporary_zone_shift=true, direct_switch=true})
-	self.change_level_check = nil
-	self.real_change = nil
 	return true
 end
-g.on_move = function(self, x, y, who)
+g.on_move = function(self, x, y, who, act, couldpass)
 	if not who or not who.player then return false end
 	if self.broken then
 		game.log("#VIOLET#The portal is already broken!")
 		return false
 	end
-
-	require("engine.ui.Dialog"):yesnoPopup("Coral Portal", "Do you wish to enter the portal or just destroy it?", function(ret)
-		game.log("#VIOLET#The portal is broken!")
+	require("engine.ui.Dialog"):yesnoPopup("Coral Portal", "Do you wish to enter the portal, destroy it, or ignore it (press escape)?", function(ret)
+		if ret == "Quit" then
+			game.log("#VIOLET#Ignoring the portal...")
+			return
+		end
 		if not ret then
 			self:change_level_check()
+		else self:break_portal()
 		end
-		self.broken = true
-		self.name = "broken "..self.name
-		self.change_level = nil
-		self.autoexplore_ignore = true
-	end, "Destroy", "Enter")
 
+	end, "Destroy", "Enter", false, "Quit")
+	
 	return false
 end
 
@@ -148,27 +161,35 @@ game.zone:addEntity(game.level, g, "terrain", x, y)
 local respawn = function(self)
 	local portal = game.level.map(self.naga_portal_x, self.naga_portal_y, engine.Map.TERRAIN)
 	if not portal or portal.broken then return end
-	local i, j = util.findFreeGrid(self.naga_portal_x, self.naga_portal_y+1, 10, true, {[engine.Map.ACTOR]=true})
-	if not i then return end
 
 	local npcs = mod.class.NPC:loadList{"/data/general/npcs/naga.lua"}
 	local m = game.zone:makeEntity(game.level, "actor", {base_list=npcs}, nil, true)
 	if not m then return end
 
-	m.naga_portal_x = self.naga_portal_x
-	m.naga_portal_y = self.naga_portal_y
-	m.naga_respawn = self.naga_respawn
-	m.exp_worth = 0
-	m.no_drops = true
-	m.faction = "vargh-republic"
-	m.on_die = function(self) self:naga_respawn() end
-	game.zone:addEntity(game.level, m, "actor", i, j)
-	game.logSeen(m, "#VIOLET#A naga steps out of the coral portal!")
+	local adjacent = util.adjacentCoords(self.naga_portal_x, self.naga_portal_y)
+	adjacent[5] = {self.naga_portal_x, self.naga_portal_y}
+
+	repeat
+		local grid = rng.tableRemove(adjacent)
+		if m:canMove(grid[1], grid[2]) then
+			m.naga_portal_x = self.naga_portal_x
+			m.naga_portal_y = self.naga_portal_y
+			m.naga_respawn = self.naga_respawn
+			m.exp_worth = 0
+			m.no_drops = true
+			m.ingredient_on_death = nil
+			m.faction = "vargh-republic"
+			m.on_die = function(self) self:naga_respawn() end
+			game.zone:addEntity(game.level, m, "actor", grid[1], grid[2])
+			game.logSeen(m, "#VIOLET#A naga steps out of the %s!", portal.name)
+			break
+		end
+	until #adjacent <= 0
 end
 
--- Spawn two that will keep on being replenished
+-- Spawn two nagas that will keep on being replenished
 local base = {naga_portal_x=x, naga_portal_y=y, naga_respawn=respawn}
 respawn(base)
 respawn(base)
 
-return true
+return x, y
diff --git a/game/modules/tome/data/general/events/old-battle-field.lua b/game/modules/tome/data/general/events/old-battle-field.lua
index 439aa63dc4084ce90a917ac804dfc53f6ae4340e..cc6def746b514d2b666172560576c3dcfb20212d 100644
--- a/game/modules/tome/data/general/events/old-battle-field.lua
+++ b/game/modules/tome/data/general/events/old-battle-field.lua
@@ -42,9 +42,19 @@ if tries < 100 then
 		local terrains = mod.class.Grid:loadList("/data/general/grids/cave.lua")
 		terrains.CAVE_LADDER_UP_WILDERNESS.change_level_shift_back = true
 		terrains.CAVE_LADDER_UP_WILDERNESS.change_zone_auto_stairs = true
+		terrains.CAVE_LADDER_UP_WILDERNESS.change_level = 1
+		terrains.CAVE_LADDER_UP_WILDERNESS.name = "ramp up to "..game.zone.name
+		terrains.CAVE_LADDER_UP_WILDERNESS.change_zone = game.zone.short_name
+		terrains.CAVE_LADDER_UP_WILDERNESS.change_level_check = function(self, who)
+			game.log("#VIOLET# The ramp crumbles as you climb it, followed by the collapse of the cavern.")
+			-- May delete old zone file here?
+			return
+		end
 		local zone = mod.class.Zone.new(id, {
 			name = "Cavern beneath tombstones",
-			level_range = {game.zone:level_adjust_level(game.level, game.zone, "actor"), game.zone:level_adjust_level(game.level, game.zone, "actor")},
+			level_range = game.zone.actor_adjust_level and {math.floor(game.zone:actor_adjust_level(game.level, game.player)*1.05),
+			math.ceil(game.zone:actor_adjust_level(game.level, game.player)*1.15)} or {game.zone.base_level, game.zone.base_level}, -- 5-15% higher levels
+			__applied_difficulty = true, -- Difficulty already applied to parent zone
 			level_scheme = "player",
 			max_level = 1,
 			actor_adjust_level = function(zone, level, e) return zone.base_level + e:getRankLevelAdjust() + level.level-1 + rng.range(-1,2) end,
@@ -52,8 +62,10 @@ if tries < 100 then
 			ambient_music = "Swashing the buck.ogg",
 			reload_lists = false,
 			persistent = "zone",
-			min_material_level = game.zone.min_material_level,
-			max_material_level = game.zone.max_material_level,
+
+			no_worldport = game.zone.no_worldport,
+			min_material_level = util.getval(game.zone.min_material_level),
+			max_material_level = util.getval(game.zone.max_material_level),
 			generator =  {
 				map = {
 					class = "engine.generator.map.Static",
@@ -110,11 +122,12 @@ if tries < 100 then
 	end
 
 	local grids = check(x, y)
+	local graves = {}
 	for i = 1, 5 do
 		local p = rng.tableRemove(grids)
 
 		local g = game.level.map(p.x, p.y, engine.Map.TERRAIN):cloneFull()
-		g.name = "grave"
+		g.name = "grave" g.x, g.y = p.x, p.y
 		g.display='&' g.color_r=255 g.color_g=255 g.color_b=255 g.notice = true
 		g.always_remember = true g.special_minimap = colors.OLIVE_DRAB
 		g:removeAllMOs()
@@ -124,25 +137,14 @@ if tries < 100 then
 		end
 		g.grow = nil g.dig = nil
 		g.special = true
+		g.graves = graves
 		g:altered()
 		g.block_move = function(self, x, y, who, act, couldpass)
 			if not who or not who.player or not act then return false end
 			if game.level.event_battlefield_entered then return false end
 			who:runStop("grave")
 			require("engine.ui.Dialog"):yesnoPopup("Grave", "Do you wish to disturb the grave?", function(ret) if ret then
-				local g = game.level.map(x, y, engine.Map.TERRAIN)
-				g:removeAllMOs()
-				if g.add_displays then
-					local ov = g.add_displays[#g.add_displays]
-					ov.image = "terrain/grave_opened_0"..rng.range(1, 3).."_64.png"
-				end
-				g.name = "grave (opened)"
-				game.level.map:updateMap(x, y)
-
-				self.block_move = nil
-				self.autoexplore_ignore = true
 				self:change_level_check()
-				require("engine.ui.Dialog"):simplePopup("Fall...", "As you tried to dig the grave the ground fell under you. You find yourself stranded in an eerie lit cavern.")
 			end end)
 			return false
 		end
@@ -150,14 +152,29 @@ if tries < 100 then
 		g.real_change = changer
 		g.change_level_check = function(self)
 			if game.level.event_battlefield_entered then return true end
+			self:removeAllMOs()
+			if self.add_displays then
+				local ov = self.add_displays[#self.add_displays]
+				ov.image = "terrain/grave_opened_0"..rng.range(1, 3).."_64.png"
+			end
+			self.name = "opened grave"
+			game.level.map:updateMap(self.x, self.y)
 			game.level.event_battlefield_entered = true
 			game:changeLevel(1, self.real_change(self.change_zone), {temporary_zone_shift=true, direct_switch=true})
+			require("engine.ui.Dialog"):simplePopup("Fall...", "As you began digging up the grave, the ground collapsed beneath you. You fall into an eerily lit cavern.")
+			for i, gr in ipairs(self.graves) do
+				gr.change_level_check = nil
+				gr.change_level = nil
+				gr.block_move = nil
+				gr.autoexplore_ignore = true
+			end
 			return true
 		end
 
+		print("[EVENT] Placing grave for event", id, "at", p.x, p.y)
 		game.zone:addEntity(game.level, g, "terrain", p.x, p.y)
-		print("[EVENT] grave placed at ", p.x, p.y)
+		table.insert(graves, g)
 	end
 end
 
-return true
+return x, y
\ No newline at end of file
diff --git a/game/modules/tome/data/general/events/rat-lich.lua b/game/modules/tome/data/general/events/rat-lich.lua
index 4ac29aa0c7835eddd623762e7f46eae7df7e5c02..a72f80d31375607dc8a65e5d1efef8805302f251 100644
--- a/game/modules/tome/data/general/events/rat-lich.lua
+++ b/game/modules/tome/data/general/events/rat-lich.lua
@@ -20,24 +20,27 @@
 -- Unique
 if game.state:doneEvent(event_id) then return end
 
--- Find a random spot
-local x, y = rng.range(1, level.map.w - 2), rng.range(1, level.map.h - 2)
-local tries = 0
-while not game.state:canEventGrid(level, x, y) and tries < 100 do
-	x, y = rng.range(1, level.map.w - 2), rng.range(1, level.map.h - 2)
-	tries = tries + 1
-end
-if tries >= 100 then return false end
+local x, y = game.state:findEventGrid(level)
+if not x then return false end
 
 local id = "rat-lich-"..game.turn
 
+print("[EVENT] Placing event", id, "at", x, y)
+
 local changer = function(id)
 	local npcs = mod.class.NPC:loadList{"/data/general/npcs/undead-rat.lua"}
 	local objects = mod.class.Object:loadList("/data/general/objects/objects.lua")
 	local terrains = mod.class.Grid:loadList("/data/general/grids/basic.lua")
 	terrains.UP_WILDERNESS.change_level_shift_back = true
 	terrains.UP_WILDERNESS.change_zone_auto_stairs = true
-
+	terrains.UP_WILDERNESS.name = "way up to "..game.zone.name
+	terrains.UP_WILDERNESS.change_zone = game.zone.short_name
+	terrains.UP_WILDERNESS.change_level = 1
+	terrains.UP_WILDERNESS.change_level_check = function(self)
+		game.log("#VIOLET# As you leave the crypt, the stairway collapses in upon itself.")
+		-- May delete old zone file here?
+		return
+	end
 	objects.RATLICH_SKULL = mod.class.Object.new{
 		define_as = "RATLICH_SKULL",
 		power_source = {arcane=true},
@@ -97,7 +100,9 @@ local changer = function(id)
 
 	local zone = mod.class.Zone.new(id, {
 		name = "Forsaken Crypt",
-		level_range = {game.zone:level_adjust_level(game.level, game.zone, "actor"), game.zone:level_adjust_level(game.level, game.zone, "actor")},
+		level_range = game.zone.actor_adjust_level and {math.floor(game.zone:actor_adjust_level(game.level, game.player)*1.05),
+			math.ceil(game.zone:actor_adjust_level(game.level, game.player)*1.15)} or {game.zone.base_level, game.zone.base_level}, -- 5-15% higher levels
+		__applied_difficulty = true, --Difficulty already applied to parent zone
 		level_scheme = "player",
 		max_level = 1,
 		actor_adjust_level = function(zone, level, e) return zone.base_level + e:getRankLevelAdjust() + level.level-1 + rng.range(-1,2) end,
@@ -105,8 +110,10 @@ local changer = function(id)
 		ambient_music = "Dark Secrets.ogg",
 		reload_lists = false,
 		persistent = "zone",
-		min_material_level = game.zone.min_material_level,
-		max_material_level = game.zone.max_material_level,
+		no_worldport = game.zone.no_worldport,
+		min_material_level = util.getval(game.zone.min_material_level),
+		max_material_level = util.getval(game.zone.max_material_level),
+		
 		generator =  {
 			map = {
 				class = "engine.generator.map.Roomer",
@@ -142,7 +149,10 @@ local changer = function(id)
 end
 
 local g = game.level.map(x, y, engine.Map.TERRAIN):cloneFull()
-g.name = "forsaken crypt"
+g.name = "stairway leading downwards"
+g.always_remember = true
+g.desc = [[Stairs seem to lead into some kind of crypt.]]
+g.show_tooltip = true
 g.display='>' g.color_r=0 g.color_g=0 g.color_b=255 g.notice = true
 g.change_level=1 g.change_zone=id g.glow=true
 g:removeAllMOs()
@@ -157,7 +167,11 @@ g.change_level_check = function(self)
 	game:changeLevel(1, self.real_change(self.change_zone), {temporary_zone_shift=true, direct_switch=true})
 	require("engine.ui.Dialog"):simplePopup("Forsaken Crypt", "You hear squeaks and the sounds of clicking bone echo around you... Pure death awaits. Flee!")
 	self.change_level_check = nil
-	self.real_change = nil
+	self.change_level = nil
+	self.name = "collapsed forsaken crypt"
+	self.desc = [[Stairs lead downwards into rubble.]]
+	self.autoexplore_ignore = true
+	self.special_minimap = colors.VIOLET
 	return true
 end
 game.zone:addEntity(game.level, g, "terrain", x, y)
@@ -172,4 +186,4 @@ for z = 1, 3 do
 	end
 end
 
-return true
+return x, y
diff --git a/game/modules/tome/data/general/events/sludgenest.lua b/game/modules/tome/data/general/events/sludgenest.lua
index 57d71eef188dca6755c03456c154001576c4ce11..280fe92fb1d8d27f86250d3b06bcd59d4d812dc4 100644
--- a/game/modules/tome/data/general/events/sludgenest.lua
+++ b/game/modules/tome/data/general/events/sludgenest.lua
@@ -37,7 +37,7 @@ level.data.on_enter_list.sludgenest = function()
 	g:initGlow()
 	game.zone:addEntity(game.level, g, "terrain", spot.x, spot.y)
 	print("[WORLDMAP] sludgenest at", spot.x, spot.y)
-	require("engine.ui.Dialog"):simpleLongPopup("Lush forest", "Suddently it comes back to you. You remember long ago somebody told you about a strange lush forest in the cold icy wastes of the northland.", 400)
+	require("engine.ui.Dialog"):simpleLongPopup("Lush forest", "Suddenly it comes back to you. You remember long ago somebody told you about a strange lush forest in the cold icy wastes of the northland.", 400)
 end
 
 return true
diff --git a/game/modules/tome/data/general/events/sub-vault.lua b/game/modules/tome/data/general/events/sub-vault.lua
index ac4e362d626af0af89133341b4ef11287380714a..e1bacc600e58471febedb8ca641f0de66512057f 100644
--- a/game/modules/tome/data/general/events/sub-vault.lua
+++ b/game/modules/tome/data/general/events/sub-vault.lua
@@ -20,8 +20,8 @@
 -- Find a random spot
 local x, y = game.state:findEventGrid(level)
 if not x then return false end
-print("placing sub-vault at", x, y)
 local id = "sub-vault"..game.turn.."-"..rng.range(1,9999)
+print("[EVENT] Placing event", id, "at", x, y)
 
 local changer = function(id)
 	local grid_list = table.clone(game.zone.grid_list)
@@ -72,9 +72,9 @@ local changer = function(id)
 		ambient_music = game.zone.ambient_music,
 		reload_lists = false,
 		persistent = "zone",
---		_max_level_generation_count = 2,
-		min_material_level = game.zone.min_material_level,
-		max_material_level = game.zone.max_material_level,
+--		_max_level_generation_count = 5,
+		min_material_level = util.getval(game.zone.min_material_level),
+		max_material_level = util.getval(game.zone.max_material_level),
 		no_worldport = game.zone.no_worldport,
 		generator =  {
 			map = table.merge(basemap, {
@@ -110,9 +110,11 @@ end
 
 local g = game.level.map(x, y, engine.Map.TERRAIN):cloneFull()
 g.name = "hidden vault"
+g.always_remember = true
 g.desc = [[Crumbling stairs lead down to something.]]
 g.show_tooltip = true
 g.display='>' g.color_r=0 g.color_g=0 g.color_b=255 g.notice = true
+g.special_minimap = colors.VIOLET
 g.change_level=1 g.change_zone=id g.glow=true
 g:removeAllMOs()
 if engine.Map.tiles.nicer_tiles then
@@ -124,19 +126,17 @@ g:altered()
 g:initGlow()
 g.real_change = changer
 g.change_level_check = function(self) -- limit stair scumming
-	game:changeLevel(1, self.real_change(self.change_zone), {temporary_zone_shift=true, direct_switch=true})
 	self._use_count = self._use_count - 1
 	self.name = "collapsing hidden vault"
 	if self._use_count < 1 then
 		self.change_level_check = nil
-		self.real_change = nil
 		self.change_level = nil
 		self.name = "collapsed hidden vault"
 		self.desc = [[A collapsed stairway, leading down]]
 	elseif self._use_count < 2 then
 		self.name = "nearly collapsed hidden vault"
 	end
-	self.special_minimap = colors.BLUE
+	game:changeLevel(1, self.real_change(self.change_zone), {temporary_zone_shift=true, direct_switch=true})
 	return true
 end
 game.zone:addEntity(game.level, g, "terrain", x, y)
diff --git a/game/modules/tome/data/zones/halfling-ruins/events.lua b/game/modules/tome/data/zones/halfling-ruins/events.lua
index 5d303d5bb45ea170276cb04b712b06549c2eb7b0..4cf5cbb07a9513b8d0b229fba51b5f61d7e91803 100644
--- a/game/modules/tome/data/zones/halfling-ruins/events.lua
+++ b/game/modules/tome/data/zones/halfling-ruins/events.lua
@@ -22,5 +22,8 @@ return { one_per_level=true,
 	{name="protective-aura", minor=true, percent=50},
 	{name="necrotic-air", minor=true, percent=50},
 	{name="glowing-chest", minor=true, percent=20},
-	{name="conclave-vault", percent=30, forbid={1, 2, 4}, special=function() if game:getPlayer(true) and game:getPlayer(true).descriptor and game:getPlayer(true).descriptor.subrace == "Shalore" then return true end end},
+	{name="conclave-vault", level_range={3, 3},
+		-- always spawn conclave-vault event for Shalore
+		percent=(table.get(game:getPlayer(true), "descriptor", "subrace") == "Shalore" and 100 or 30)
+	}
 }
diff --git a/game/modules/tome/data/zones/infinite-dungeon/events.lua b/game/modules/tome/data/zones/infinite-dungeon/events.lua
index b7dd02f4099df64e6a476edbf6608e21b6756a3e..3ff5e452b3041cc814169ac57ff15918bf44199d 100644
--- a/game/modules/tome/data/zones/infinite-dungeon/events.lua
+++ b/game/modules/tome/data/zones/infinite-dungeon/events.lua
@@ -19,7 +19,7 @@
 
 return { one_per_level=true,
 	{group="majeyal-generic", percent_factor=0.5},
-	{name="cultists", percent=5},
+	{name="cultists", percent=5, level_range={15,nil}},
 	{name="icy-ground", minor=true, percent=20},
 	{name="font-life", minor=true, percent=20},
 	{name="whistling-vortex", minor=true, percent=20},
@@ -31,4 +31,12 @@ return { one_per_level=true,
 	{name="protective-aura", minor=true, percent=20},
 	{name="spellblaze-scar", minor=true, percent=20},
 	{name="glowing-chest", minor=true, percent=30},
+
+	{name="rat-lich", percent=5, level_range={2,20}, unique=true},
+	{name="old-battle-field", percent=5, level_range={8,40}},
+	{name="damp-cave", percent=5, level_range={2,20}},
+	{name="drake-cave", percent=5, level_range={10,nil}},
+	{name="fearscape-portal", percent=5, level_range={18,nil}},
+	{name="naga-portal", percent=5, level_range={15,nil}},
+
 }
diff --git a/game/modules/tome/data/zones/wilderness/events.lua b/game/modules/tome/data/zones/wilderness/events.lua
index 1270721f366d93e812815a4165662d7787554e44..1719ef02d5f4ce0851a9b632da92f816851a407e 100644
--- a/game/modules/tome/data/zones/wilderness/events.lua
+++ b/game/modules/tome/data/zones/wilderness/events.lua
@@ -17,7 +17,11 @@
 -- Nicolas Casalini "DarkGod"
 -- darkgod@te4.org
 
+local descriptor = table.get(game:getPlayer(true), "descriptor")
+
 return {
-	{name="noxious-caldera", percent=30, special=function() if game:getPlayer(true) and game:getPlayer(true).descriptor and game:getPlayer(true).descriptor.race == "Yeek" then return true end end},
-	{name="sludgenest", percent=30, special=function() if game:getPlayer(true) and game:getPlayer(true).descriptor and game:getPlayer(true).descriptor.subrace == "Thalore" then return true end end},
+	-- always spawn noxious-caldera for Yeeks (event file does additional tests)
+	{name="noxious-caldera", percent=(table.get(descriptor, "race") == "Yeek" and 100 or 30)},
+	 -- always spawn sludgenest for Thalore (event file does additional tests)
+	{name="sludgenest", percent=(table.get(descriptor, "subrace") == "Thalore" and 100 or 30)},
 }