diff --git a/game/engines/default/engine/Zone.lua b/game/engines/default/engine/Zone.lua
index c935e912b070a18d0beb36ae95a0d0747d4d680f..cd7e91246889f1775c80a70fe9167650953e00a7 100644
--- a/game/engines/default/engine/Zone.lua
+++ b/game/engines/default/engine/Zone.lua
@@ -1003,7 +1003,7 @@ function _M:newLevel(level_data, lev, old_lev, game)
 	local ux, uy, dx, dy, spots = generator:generate(lev, old_lev)
 	if level.force_recreate then
 
-		forceprint("[Zone:newLevel] map generator"..generator.__CLASSNAME.." forced recreation: ",level.force_recreate)
+		forceprint("[Zone:newLevel] map generator "..generator.__CLASSNAME.." forced recreation: ",level.force_recreate)
 		level:removed()
 		return self:newLevel(level_data, lev, old_lev, game)
 	end
diff --git a/game/engines/default/engine/generator/map/Building.lua b/game/engines/default/engine/generator/map/Building.lua
index 3e9ff6540a5605bcaa8e7d17ff0329cc4c8fd2a5..16000ccca4fa688a156fe6f38bb4f4e8b939d86f 100644
--- a/game/engines/default/engine/generator/map/Building.lua
+++ b/game/engines/default/engine/generator/map/Building.lua
@@ -26,20 +26,28 @@ local RoomsLoader = require "engine.generator.map.RoomsLoader"
 --- @classmod engine.generator.map.Building
 module(..., package.seeall, class.inherit(engine.Generator, RoomsLoader))
 
+--- populate the map with clusters of buildings ("blocks") divided into rooms ("buildings") by walls and corridors
+-- special grids (in data):
+--		external_floor = grid to use between buildings (rooms)
+--		outside_floor = grid to use outside the building area
 function _M:init(zone, map, level, data)
 	engine.Generator.init(self, zone, map, level)
 	self.data = data
 	self.grid_list = self.zone.grid_list
-	self.max_block_w = data.max_block_w or 20
-	self.max_block_h = data.max_block_h or 20
-	self.max_building_w = data.max_building_w or 7
-	self.max_building_h = data.max_building_h or 7
-	self.margin_w = data.margin_w or 0
-	self.margin_h = data.margin_h or 0
-
+	self.max_block_w = data.max_block_w or 20 -- max width of a "block" (map partition)
+	self.max_block_h = data.max_block_h or 20 -- max height of a "block" (map partition)
+	self.max_building_w = data.max_building_w or 7 -- ~ max building width (width >= 2x this forces partitioning)
+	self.max_building_h = data.max_building_h or 7 -- ~ max building height (height >= 2x this forces partitioning)
+	self.margin_w = data.margin_w or 0 -- horizontal map border (outside of building area)
+	self.margin_h = data.margin_h or 0 -- vertical map border (outside of building area)
+	self.data.outside_floor = self.data.outside_floor or self.data.external_floor
+	self.max_room_w = data.max_room_w or (map.w - 2*self.margin_w)/2 - 1 -- maximum room width
+	self.max_room_h = data.max_room_h or (map.h - 2*self.margin_h)/2 - 1 -- maximum room height
 	RoomsLoader.init(self, data)
+	
 end
 
+--- place a door in a wall
 function _M:doorOnWall(wall)
 	if wall.vert then
 		local j = rng.table(table.keys(wall.ps))
@@ -51,23 +59,29 @@ function _M:doorOnWall(wall)
 	wall.doored = true
 end
 
+--- designate a line of grids as a wall and mark possible door spots
 function _M:addWall(vert, base, p1, p2)
 	local walls = self.walls
 	local ps = table.genrange(p1, p2, true)
 
-	local todel = {}
 	for z, _ in pairs(ps) do
 		local x, y
 		if vert then x, y = base, z else x, y = z, base end
-		local nb = 
-			(game.level.map:checkEntity(x - 1, y, Map.TERRAIN, "block_move") and 1 or 0) + 
-			(game.level.map:checkEntity(x + 1, y, Map.TERRAIN, "block_move") and 1 or 0) + 
-			(game.level.map:checkEntity(x, y - 1, Map.TERRAIN, "block_move") and 1 or 0) + 
-			(game.level.map:checkEntity(x, y + 1, Map.TERRAIN, "block_move") and 1 or 0)
-		if nb ~= 2 then todel[#todel+1] = z end
+		local rm_map = self.map.room_map[x][y]
+		if rm_map.special or (rm_map.room and not rm_map.can_open) then
+			ps[z] = nil
+		else
+			rm_map.walled = true
+			if z == p1 or z == p2 or not game.level.map:checkEntity(x, y, Map.TERRAIN, "block_move") or 2 ~= 
+				(game.level.map:checkEntity(x - 1, y, Map.TERRAIN, "block_move") and 1 or 0) + 
+				(game.level.map:checkEntity(x + 1, y, Map.TERRAIN, "block_move") and 1 or 0) + 
+				(game.level.map:checkEntity(x, y - 1, Map.TERRAIN, "block_move") and 1 or 0) + 
+				(game.level.map:checkEntity(x, y + 1, Map.TERRAIN, "block_move") and 1 or 0) then 
+					ps[z] = nil
+			end
+		end
 	end
-	for i, z in ipairs(todel) do ps[z] = nil end
-
+	-- remove duplicate door spots in any overlapping walls
 	for i = 1, #walls do
 		local w = walls[i]
 		if w.vert == vert and w.base == base then 
@@ -76,78 +90,59 @@ function _M:addWall(vert, base, p1, p2)
 	end
 
 	walls[#walls+1] = {vert=vert, base=base, ps=ps}
-
+--	print(("\tAdding wall[%d]: %s:%d, (%d-%d)"):format(#walls, vert and "column" or "row", base, p1, p2))
 	return walls[#walls]
 end
 
+--- create a "building" as an area within a "block" enclosed by walls
 function _M:building(leaf, spots)
---	local x1, x2 = leaf.rx + rng.range(2, math.max(2, math.floor(leaf.w / 2 - 3))), leaf.rx + leaf.w - rng.range(2, math.max(2, math.floor(leaf.w / 2 - 3)))
---	local y1, y2 = leaf.ry + rng.range(2, math.max(2, math.floor(leaf.h / 2 - 3))), leaf.ry + leaf.h - rng.range(2, math.max(2, math.floor(leaf.h / 2 - 3)))
 	local x1, x2 = leaf.rx, leaf.rx + leaf.w
 	local y1, y2 = leaf.ry, leaf.ry + leaf.h
 	local ix1, ix2, iy1, iy2 = x1 + 2, x2 - 1, y1 + 2, y2 - 1
-	local inner_grids = {}
-	local door_grids = {}
 	local is_lit = rng.percent(self.data.lite_room_chance or 70)
 
-	for i = leaf.rx, leaf.rx + leaf.w do for j = leaf.ry, leaf.ry + leaf.h do
-		-- Abort if there is something already
-		if self.map:isBound(i, j) and self.map.room_map[i][j].room then return end
-	end end
-
+	-- populate leaf grids
 	for i = x1, x2 do for j = y1, y2 do
-		if i == x1 or i == x2 or j == y1 or j == y2 then
-			if not self.map.room_map[i][j].walled then self.map(i, j, Map.TERRAIN, self:resolve("wall")) end
-			self.map.room_map[i][j].walled = true
-			self.map.room_map[i][j].can_open = true
-		else
-			self.map(i, j, Map.TERRAIN, self:resolve("floor"))
-			if is_lit then self.map.lites(i, j, true) end
-			if i >= ix1 and i <= ix2 and j >= iy1 and j <= iy2 then
-				inner_grids[#inner_grids+1] = {x=i,y=j}
+		local rm_map = self.map.room_map[i][j]
+		if not rm_map.special and (not rm_map.room or rm_map.can_open) then
+			if i == x1 or i == x2 or j == y1 or j == y2 then
+				if not rm_map.walled then self.map(i, j, Map.TERRAIN, self:resolve("wall")) end
+				rm_map.walled = true
+				rm_map.can_open = true
+			elseif not rm_map.walled then
+				self.map(i, j, Map.TERRAIN, self:resolve("floor"))
+				if is_lit then self.map.lites(i, j, true) end
 			end
 		end
 	end end
-
+	
 	local walls = {}
-	walls.up = self:addWall(false, y1, x1 + 1, x2 - 1)
-	walls.down = self:addWall(false, y2, x1 + 1, x2 - 1)
-	walls.left = self:addWall(true,  x1, y1 + 1, y2 - 1)
-	walls.right = self:addWall(true,  x2, y1 + 1, y2 - 1)
+	walls.up = self:addWall(false, y1, x1, x2)
+	walls.down = self:addWall(false, y2, x1, x2)
+	walls.left = self:addWall(true,  x1, y1, y2)
+	walls.right = self:addWall(true,  x2, y1, y2)
 	self.rooms[#self.rooms+1] = {cx=math.floor((x1+x2)/2), cy=math.floor((y1+y2)/2), id=#self.rooms, walls=walls}
 
-	-- Eliminate inner grids that face the door
-	for i = #inner_grids, 1, -1 do
-		local g = inner_grids[i]
-		if door and (g.x == door.x or g.y == door.y) then table.remove(inner_grids, i) end
-	end
-
 	spots[#spots+1] = {x=math.floor((x1+x2)/2), y=math.floor((y1+y2)/2), type="building", subtype="building"}
 end
 
+--- create a "block" as a contiguous partition of the map containing "buildings"
 function _M:block(leaf, spots)
 	local x1, x2 = leaf.rx, leaf.rx + leaf.w
 	local y1, y2 = leaf.ry, leaf.ry + leaf.h
 	local ix1, ix2, iy1, iy2 = x1 + 2, x2 - 1, y1 + 2, y2 - 1
-	local inner_grids = {}
-	local door_grids = {}
 
-	for i = leaf.rx, leaf.rx + leaf.w do for j = leaf.ry, leaf.ry + leaf.h do
-		-- Abort if there is something already
-		if self.map:isBound(i, j) and self.map.room_map[i][j].room then return end
-	end end
-
-	local door_grids = {}
+	-- populate grids around the border
 	for i = x1, x2 do for j = y1, y2 do
-		if i == x1 or i == x2 or j == y1 or j == y2 then
-			self.map(i, j, Map.TERRAIN, self:resolve("floor"))
+		if (i == x1 or i == x2 or j == y1 or j == y2) and not self.map.room_map[i][j].special and (not self.map.room_map[i][j].room or self.map.room_map[i][j].can_open) then
+			self.map(i, j, Map.TERRAIN, self:resolve("external_floor") or self:resolve("floor"))
 		end
 	end end
 
 	local bsp = BSP.new(leaf.w-2, leaf.h-2, self.max_building_w, self.max_building_h)
 	bsp:partition()
 
-	print("Building gen made ", #bsp.leafs, "building BSP leafs")
+	print("Building gen made ", #bsp.leafs, "buildings (BSP leafs)")
 	for z, sleaf in ipairs(bsp.leafs) do
 		sleaf.rx = sleaf.rx + leaf.rx + 1
 		sleaf.ry = sleaf.ry + leaf.ry + 1
@@ -157,22 +152,93 @@ end
 
 function _M:generate(lev, old_lev)
 	for i = 0, self.map.w - 1 do for j = 0, self.map.h - 1 do
-		self.map(i, j, Map.TERRAIN, self:resolve("wall"))
+		if i <= self.margin_w - 1 or i >= self.map.w - self.margin_w + 0 or j <= self.margin_h - 1 or j >= self.map.h - self.margin_h + 0 then
+			self.map(i, j, Map.TERRAIN, self:resolve("outside_floor")) -- outside of building space
+		else
+			self.map(i, j, Map.TERRAIN, self:resolve("external_floor") or self:resolve("floor")) -- between 'buildings'
+		end
 	end end
-
 	local spots = {}
 	self.spots = spots
 	self.walls = {}
-	self.rooms = {}
 
-	local bsp = BSP.new(self.map.w - self.margin_w, self.map.h - self.margin_h, self.max_block_w, self.max_block_h)
-	bsp:partition()
+	-- place specific rooms if needed
+	local nb_room = util.getval(self.data.nb_rooms or 0)
+	local rooms = self.map.room_map.rooms
+
+	-- make sure rooms meet size limits and are placed inside the margins
+	local add_check = function(room, x, y)
+		local border = room.border or 0
+		if room.w + 2*border > self.max_room_w or room.h > self.max_room_h + 2*border then
+			return false
+		end
+		if x <= self.margin_w + border or x + room.w + border >= self.map.w - self.margin_w or y <= self.margin_h + border or y + room.h + border >= self.map.h - self.margin_h then
+			return false
+		end
+		return true
+	end
+
+	print("[Building] placing", nb_room, "rooms")
+	-- Place required rooms
+	if #self.required_rooms > 0 then
+		for i, rroom in ipairs(self.required_rooms) do
+			local ok = false
+			if type(rroom) == "table" and rroom.chance_room then
+				if rng.percent(rroom.chance_room) then rroom = rroom[1] ok = true end
+			else ok = true
+			end
 
-	print("Building gen made ", #bsp.leafs, "blocks BSP leafs")
+			if ok then
+				local r = self:roomAlloc(rroom, #rooms+1, lev, old_lev, add_check)
+				if r then nb_room = nb_room - 1
+				else self.level.force_recreate = "required_room "..tostring(rroom) return end
+			end
+		end
+	end
+	
+	-- Place normal, random rooms
+	local tries = nb_room * 1.5 -- allow for extra attempts for difficult to place rooms
+	while tries > 0 and nb_room > 0 do
+		local rroom
+		while true do
+			rroom = self.rooms[rng.range(1, #self.rooms)]
+			print("[Building] picked random room", rroom)
+			if type(rroom) == "table" and rroom.chance_room then
+				if rng.percent(rroom.chance_room) then rroom = rroom[1] break end
+			else
+				break
+			end
+		end
+
+		local r = self:roomAlloc(rroom, #rooms+1, lev, old_lev, add_check)
+		if r then nb_room = nb_room -1 end
+		tries = tries - 1
+	end
+
+	-- enable doors in edges of each room placed
+	for i, r in ipairs(rooms) do
+		if not r.room.no_tunnels then -- don't add doors to unconnected rooms
+			local walls = {} -- mark edges of room as walls
+			r.walls = walls
+			walls.top = self:addWall(false, r.y, r.x , r.x + r.room.w - 1)
+			walls.bottom = self:addWall(false, r.y + r.room.h - 1, r.x , r.x + r.room.w - 1)
+			walls.left = self:addWall(true, r.x, r.y, r.y + r.room.h - 1)
+			walls.right = self:addWall(true, r.x + r.room.w - 1, r.y, r.y + r.room.h - 1)
+		end
+	end
+	
+	-- partition the map into "blocks"
+	local bsp = BSP.new(self.map.w - 2*self.margin_w, self.map.h - 2*self.margin_h, self.max_block_w, self.max_block_h)
+	local store = {x=self.margin_w, y=self.margin_h, rx=self.margin_w, ry=self.margin_h, w=self.map.w - 2*self.margin_w - 1, h=self.map.h - 2*self.margin_h - 1, nodes={}, id=0, depth=0}
+	bsp:partition(store)
+	print("[Building] generator made ", #bsp.leafs, "blocks (BSP leafs)")
+	
+	-- resolve each "block"
 	for z, leaf in ipairs(bsp.leafs) do
 		self:block(leaf, spots)
 	end
-
+	
+	-- place a door in each designated wall
 	for i = 1, #self.walls do
 		self:doorOnWall(self.walls[i])
 	end
@@ -187,13 +253,14 @@ function _M:generate(lev, old_lev)
 	return ux, uy, dx, dy, spots
 end
 
---- Create the stairs inside the level
+--- Create the stairs inside the level (inside the margin, if any)
 function _M:makeStairsInside(lev, old_lev, spots)
+	local m_w, m_h = self.margin_w, self.margin_h
 	-- Put down stairs
 	local dx, dy
 	if lev < self.zone.max_level or self.data.force_last_stair then
 		while true do
-			dx, dy = rng.range(1, self.map.w - 1), rng.range(1, self.map.h - 1)
+			dx, dy = rng.range(m_w + 1, self.map.w - m_w - 1), rng.range(m_h + 1, self.map.h - m_h - 1)
 			if not self.map:checkEntity(dx, dy, Map.TERRAIN, "block_move") and not self.map.room_map[dx][dy].special then
 				self.map(dx, dy, Map.TERRAIN, self:resolve("down"))
 				self.map.room_map[dx][dy].special = "exit"
@@ -205,7 +272,7 @@ function _M:makeStairsInside(lev, old_lev, spots)
 	-- Put up stairs
 	local ux, uy
 	while true do
-		ux, uy = rng.range(1, self.map.w - 1), rng.range(1, self.map.h - 1)
+		ux, uy = rng.range(m_w + 1, self.map.w - m_w - 1), rng.range(m_h + 1, self.map.h - m_h - 1)
 		if not self.map:checkEntity(ux, uy, Map.TERRAIN, "block_move") and not self.map.room_map[ux][uy].special then
 			self.map(ux, uy, Map.TERRAIN, self:resolve("up"))
 			self.map.room_map[ux][uy].special = "exit"
@@ -216,16 +283,17 @@ function _M:makeStairsInside(lev, old_lev, spots)
 	return ux, uy, dx, dy, spots
 end
 
---- Create the stairs on the sides
+--- Create the stairs on the sides (inside the margin, if any)
 function _M:makeStairsSides(lev, old_lev, sides, spots)
+	local m_w, m_h = self.margin_w, self.margin_h
 	-- Put down stairs
 	local dx, dy
 	if lev < self.zone.max_level or self.data.force_last_stair then
 		while true do
-			if     sides[2] == 4 then dx, dy = 0, rng.range(0, self.map.h - 1)
-			elseif sides[2] == 6 then dx, dy = self.map.w - 1, rng.range(0, self.map.h - 1)
-			elseif sides[2] == 8 then dx, dy = rng.range(0, self.map.w - 1), 0
-			elseif sides[2] == 2 then dx, dy = rng.range(0, self.map.w - 1), self.map.h - 1
+			if     sides[2] == 4 then dx, dy = m_w, rng.range(m_h, self.map.h - m_h - 1)
+			elseif sides[2] == 6 then dx, dy = self.map.w - m_w - 1, rng.range(m_h, self.map.h - m_h - 1)
+			elseif sides[2] == 8 then dx, dy = rng.range(m_w, self.map.w - m_w - 1), m_h
+			elseif sides[2] == 2 then dx, dy = rng.range(m_w, self.map.w - m_w - 1), self.map.h- m_h - 1
 			end
 
 			if not self.map.room_map[dx][dy].special then
@@ -239,12 +307,12 @@ function _M:makeStairsSides(lev, old_lev, sides, spots)
 	-- Put up stairs
 	local ux, uy
 	while true do
-		if     sides[1] == 4 then ux, uy = 0, rng.range(0, self.map.h - 1)
-		elseif sides[1] == 6 then ux, uy = self.map.w - 1, rng.range(0, self.map.h - 1)
-		elseif sides[1] == 8 then ux, uy = rng.range(0, self.map.w - 1), 0
-		elseif sides[1] == 2 then ux, uy = rng.range(0, self.map.w - 1), self.map.h - 1
+		if     sides[1] == 4 then ux, uy = m_w, rng.range(m_h, self.map.h - m_h - 1)
+		elseif sides[1] == 6 then ux, uy = self.map.w - m_w - 1, rng.range(m_h, self.map.h - m_h - 1)
+		elseif sides[1] == 8 then ux, uy = rng.range(m_w, self.map.w - m_w - 1), m_h
+		elseif sides[1] == 2 then ux, uy = rng.range(m_w, self.map.w - m_w - 1), self.map.h- m_h - 1
 		end
-
+			
 		if not self.map.room_map[ux][uy].special then
 			self.map(ux, uy, Map.TERRAIN, self:resolve("up"))
 			self.map.room_map[ux][uy].special = "exit"
diff --git a/game/engines/default/engine/generator/map/RoomsLoader.lua b/game/engines/default/engine/generator/map/RoomsLoader.lua
index bc36e9dd1fb431604926ca427dca5a1a2c9bc8a1..2558d12a9fca6f5b24eff60c6cd1d7ee5553d243 100644
--- a/game/engines/default/engine/generator/map/RoomsLoader.lua
+++ b/game/engines/default/engine/generator/map/RoomsLoader.lua
@@ -34,16 +34,20 @@ function _M:init(data)
 	if data.rooms then for i, file in ipairs(data.rooms) do
 		if type(file) == "table" then
 			table.insert(self.rooms, {self:loadRoom(file[1]), chance_room=file[2]})
+			print("[RoomsLoader:init] loaded room:", file[1], self.rooms[#self.rooms][1], "chance:", file[2])
 		else
 			table.insert(self.rooms, self:loadRoom(file))
+			print("[RoomsLoader:init] loaded room:", file, self.rooms[#self.rooms])
 		end
 	end end
 
 	if data.required_rooms then for i, file in ipairs(data.required_rooms) do
 		if type(file) == "table" then
 			table.insert(self.required_rooms, {self:loadRoom(file[1]), chance_room=file[2]})
+			print("[RoomsLoader:init] loaded required room:", file[1], self.required_rooms[#self.required_rooms][1], "chance:", file[2])
 		else
 			table.insert(self.required_rooms, self:loadRoom(file))
+			print("[RoomsLoader:init] loaded required room:", file, self.required_rooms[#self.required_rooms])
 		end
 	end end
 end
@@ -564,7 +568,7 @@ function _M:roomCheck(room, zone, level, map)
 			for i, xroom in ipairs(rooms_list) do
 				if xroom.room then
 					if xroom.room.unique == room.unique then
-						print("[roomCheck]-- rejecting duplicate of unique room", room.unique)
+						print("[RoomsLoader:roomCheck]-- rejecting duplicate of unique room", room.unique)
 						return false, "unique:"..room.unique
 					end
 				end
@@ -658,8 +662,7 @@ function _M:roomPlace(room, id, x, y)
 			end
 		end
 	end
-	
-	print("room placed at", x, y,"with center",math.floor(x+(room.w-1)/2), math.floor(y+(room.h-1)/2))
+	print("placed room", room.name ,"at", x, y,"with center",math.floor(x+(room.w-1)/2), math.floor(y+(room.h-1)/2))
 	cx = cx or math.floor(x+(room.w-1)/2)
 	cy = cy or math.floor(y+(room.h-1)/2)
 	local ret = { id=id, x=x, y=y, cx=cx, cy=cy, room=room }
@@ -705,7 +708,7 @@ function _M:roomAlloc(room, id, lev, old_lev, add_check)
 		if px and py then -- gradually spread additional tries around the preferred location
 			local sig = tries/100
 			x = util.bound(rng.normal(px, sig*self.map.w*2), math.max(1, border), self.map.w - room.w - math.max(1, border))
-			y = util.bound(rng.normal(px, sig*self.map.h*2), math.max(1, border), self.map.h - room.h - math.max(1, border))
+			y = util.bound(rng.normal(py, sig*self.map.h*2), math.max(1, border), self.map.h - room.h - math.max(1, border))
 		else
 			x = rng.range(math.max(1, border), self.map.w - room.w - math.max(1, border))
 			y = rng.range(math.max(1, border), self.map.h - room.h - math.max(1, border))