diff --git a/game/engines/default/engine/CharacterBallSave.lua b/game/engines/default/engine/CharacterBallSave.lua
new file mode 100644
index 0000000000000000000000000000000000000000..634c397e77fa45182965e5e320d96e7d09043425
--- /dev/null
+++ b/game/engines/default/engine/CharacterBallSave.lua
@@ -0,0 +1,49 @@
+-- TE4 - T-Engine 4
+-- Copyright (C) 2009, 2010, 2011 Nicolas Casalini
+--
+-- This program is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- This program is distributed in the hope that it will be useful,
+-- but WITHOUT ANY WARRANTY; without even the implied warranty of
+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+-- GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License
+-- along with this program.  If not, see <http://www.gnu.org/licenses/>.
+--
+-- Nicolas Casalini "DarkGod"
+-- darkgod@te4.org
+
+require "engine.class"
+local Savefile = require "engine.Savefile"
+
+--- Handles a local characters vault saves
+module(..., package.seeall, class.inherit(Savefile))
+
+function _M:init(savefile, coroutine)
+	Savefile.init(self, savefile, coroutine)
+
+	fs.mkdir("/charballs")
+	self.short_name = savefile:gsub("[^a-zA-Z0-9_-.]", "_")
+	self.save_dir = "/charballs/"
+	self.quickbirth_file = "/charballs/useless.quickbirth"
+	self.load_dir = "/tmp/loadsave/"
+end
+
+--- Get a savename for an entity
+function _M:nameSaveEntity(e)
+	e.__version = game.__mod_info.version
+	return ("%s-%d.%d.%d.charball"):format(e.__te4_uuid, game.__mod_info.version[1], game.__mod_info.version[2], game.__mod_info.version[3])
+end
+--- Get a savename for an entity
+function _M:nameLoadEntity(name)
+	return name..".charball"
+end
+
+--- Save an entity
+function _M:saveEntity(e, no_dialog)
+	Savefile.saveEntity(self, e, no_dialog)
+end
diff --git a/game/engines/default/engine/PlayerProfile.lua b/game/engines/default/engine/PlayerProfile.lua
index 1fa909129b764c0accaaed1bf6e153fb10753a96..c61a46c6d916979335953aeea0f367405924f9df 100644
--- a/game/engines/default/engine/PlayerProfile.lua
+++ b/game/engines/default/engine/PlayerProfile.lua
@@ -627,6 +627,16 @@ function _M:registerNewCharacter(module)
 	return uuid
 end
 
+function _M:registerSaveCharball(module, uuid, data)
+	if not self.auth or not self.hash_valid then return end
+	core.profile.pushOrder(table.serialize{o="SaveCharball",
+		module=module,
+		uuid=uuid,
+		data=data,
+	})
+	print("[ONLINE PROFILE] saved character charball", uuid)
+end
+
 function _M:registerSaveChardump(module, uuid, title, tags, data)
 	if not self.auth or not self.hash_valid then return end
 	core.profile.pushOrder(table.serialize{o="SaveChardump",
diff --git a/game/engines/default/engine/SavefilePipe.lua b/game/engines/default/engine/SavefilePipe.lua
index 56fe8c88ac7bca0c14f0b62a89a1c728b9e14368..fc7f2437b7dd359815b2a4ec19301e5abd950961 100644
--- a/game/engines/default/engine/SavefilePipe.lua
+++ b/game/engines/default/engine/SavefilePipe.lua
@@ -49,7 +49,8 @@ end
 -- @param type the Savefile method to use. I.e: "game", "level", "zone". This will cann the Savefile:saveGame, Savefile:saveLevel, Savefile:saveZone methods
 -- @param object the object to save
 -- @param class a class name, if different from the default one
-function _M:push(savename, type, object, class)
+-- @param on_end a function to call when this object is saved; this can be nil
+function _M:push(savename, type, object, class, on_end)
 	if game.onSavefilePush then game:onSavefilePush(savename, type, object, class) end
 
 	local screenshot = nil
@@ -64,7 +65,7 @@ function _M:push(savename, type, object, class)
 	if #self.pipe == 0 then savefile_pipe.current_nb = 0 end
 
 	local clone, nb = object:cloneFull()
-	self.pipe[#self.pipe+1] = {id=id, savename = savename, type=type, object=clone, nb_objects=nb, baseobject=object, class=class, saveversion=game:saveVersion("new"), screenshot=screenshot}
+	self.pipe[#self.pipe+1] = {id=id, savename = savename, type=type, object=clone, nb_objects=nb, baseobject=object, class=class, saveversion=game:saveVersion("new"), screenshot=screenshot, on_end=on_end}
 	local total_nb = 0
 	for i, p in ipairs(self.pipe) do total_nb = total_nb + p.nb_objects end
 	self.total_nb = total_nb
@@ -105,6 +106,10 @@ function _M:doThread()
 		if p.screenshot then save:saveScreenshot(p.screenshot) end
 		save:close()
 
+		if p.on_end then
+			p.on_end(save)
+		end
+
 		table.remove(self.pipe, 1)
 	end
 	if game.log then game.log("Saving done.") end
diff --git a/game/engines/default/engine/interface/PlayerDumpJSON.lua b/game/engines/default/engine/interface/PlayerDumpJSON.lua
index e32ac9ce9c7fa181b6d1a3afc229f2a0aa47e18c..d23b2e2c1d196edcb2245fd1b0c38208b7e6500f 100644
--- a/game/engines/default/engine/interface/PlayerDumpJSON.lua
+++ b/game/engines/default/engine/interface/PlayerDumpJSON.lua
@@ -35,7 +35,7 @@ function _M:getUUID()
 end
 
 --- Call this when a character is saved to upload data to te4.org
-function _M:saveUUID()
+function _M:saveUUID(do_charball)
 	if not self.__te4_uuid then
 		-- Try to grab an UUID even after char reg
 		if self.allow_late_uuid and not game:isTainted() then self:getUUID() end
@@ -58,6 +58,22 @@ function _M:saveUUID()
 	if not data or not title then return end
 
 	profile:registerSaveChardump(game.__mod_info.short_name, self.__te4_uuid, title, tags, core.zlib.compress(data))
+	if do_charball then
+		savefile_pipe:push(do_charball.name, "entity", do_charball, "engine.CharacterBallSave", function(save)
+			f = fs.open("/charballs/"..save:nameSaveEntity(do_charball), "r")
+			if f then
+				local data = {}
+				while true do
+					local l = f:read()
+					if not l then break end
+					data[#data+1] = l
+				end
+				f:close()
+
+				profile:registerSaveCharball(game.__mod_info.short_name, self.__te4_uuid, table.concat(data))
+			end
+		end)
+	end
 end
 
 --- Override this method to define dump sections
diff --git a/game/modules/tome/class/Game.lua b/game/modules/tome/class/Game.lua
index 78e409122d8d6977417bee66fc2661ed11c4e870..aa5ab4da246187058a31ab36bb6d5c634fff0a52 100644
--- a/game/modules/tome/class/Game.lua
+++ b/game/modules/tome/class/Game.lua
@@ -126,7 +126,12 @@ function _M:run()
 	self.uiset.npcs_display.actor = self.player
 
 	-- Run the current music if any
-	self:onTickEnd(function() self:playMusic() end)
+	self:onTickEnd(function()
+		self:playMusic()
+		if self.level then
+			self.level.map:moveViewSurround(self.player.x, self.player.y, config.settings.tome.scroll_dist, config.settings.tome.scroll_dist)
+		end
+	end)
 
 	-- Create the map scroll text overlay
 	local lfont = core.display.newFont("/data/font/Vera.ttf", 30)
@@ -162,7 +167,7 @@ function _M:setPlayerName(name)
 end
 
 function _M:newGame()
-	self.party = Party.new()
+	self.party = Party.new{}
 	local player = Player.new{name=self.player_name, game_ender=true}
 	self.party:addMember(player, {
 		control="full",
@@ -1584,7 +1589,12 @@ function _M:saveGame()
 	if not self.creating_player then
 		local oldplayer = self.player
 		self.party:setPlayer(self:getPlayer(true), true)
-		self.player:saveUUID()
+
+		local party = self.party:cloneFull()
+		party.__te4_uuid = self:getPlayer(true).__te4_uuid
+		for m, _ in pairs(party.members) do m.fov = {actors={}, actors_dist={}} end
+		self.player:saveUUID(party)
+
 		self.party:setPlayer(oldplayer, true)
 	end
 	self.log("Saving game...")
diff --git a/game/modules/tome/class/Party.lua b/game/modules/tome/class/Party.lua
index 27f4091f724734a2e2210292ce20c3916d3af328..c5b47d3042eb9193a8b784c101f3eb220a3d6413 100644
--- a/game/modules/tome/class/Party.lua
+++ b/game/modules/tome/class/Party.lua
@@ -29,6 +29,7 @@ module(..., package.seeall, class.inherit(
 ))
 
 function _M:init(t, no_default)
+	t.name = t.name or "party"
 	engine.Entity.init(self, t, no_default)
 
 	self.members = {}
diff --git a/game/modules/tome/class/interface/PlayerDumpJSON.lua b/game/modules/tome/class/interface/PlayerDumpJSON.lua
index 2259ce5e722b6bdb6edd9a445a081d50a9603388..bcfbe526acfef9833e866f7e27171cb932de5b66 100644
--- a/game/modules/tome/class/interface/PlayerDumpJSON.lua
+++ b/game/modules/tome/class/interface/PlayerDumpJSON.lua
@@ -296,7 +296,7 @@ function _M:dumpToJSON(js)
 	-- Log
 	-------------------------------------------------------------------
 	local log = js:newSection("last messages", "log", "text", nil)
-	log[#log+1] = { val=table.concat(game.logdisplay:getLines(30), "#LAST#\n"), bg="000000" }
+	log[#log+1] = { val=table.concat(game.uiset.logdisplay:getLines(30), "#LAST#\n"), bg="000000" }
 
 	-- Cleanup numbers
 	for _, sec in ipairs(js.sections) do
diff --git a/game/modules/tome/data/general/objects/boss-artifacts.lua b/game/modules/tome/data/general/objects/boss-artifacts.lua
index 4cdf4a9764595495f0fcc6c2e4ce31017f1d65f8..159483ce986b20bf2fec6ff3e50424b7a8be8a50 100644
--- a/game/modules/tome/data/general/objects/boss-artifacts.lua
+++ b/game/modules/tome/data/general/objects/boss-artifacts.lua
@@ -1228,7 +1228,7 @@ It has been kept somewhat intact with layers of salt and clay, but in spite of t
 
 			for x, yy in pairs(grids) do for y, _ in pairs(yy) do
 				local trap = game.level.map(x, y, Map.TRAP)
-				if trap and not (trap:knownBy(self) or trap:knownBy(who)) then
+				if trap and not (trap:knownBy(self) or trap:knownBy(actor)) then
 					is_trap = true
 					-- Set the artifact as knowing the trap, not the wearer
 					trap:setKnown(self, true)
diff --git a/game/profile-thread/Client.lua b/game/profile-thread/Client.lua
index a3f14c7194b30dc5eb3f01c480b3ca3b3e38f261..f1d9517369daa3b8a51d348a567d6ff8bfff9644 100644
--- a/game/profile-thread/Client.lua
+++ b/game/profile-thread/Client.lua
@@ -345,6 +345,13 @@ function _M:orderSaveChardump(o)
 	cprofile.pushEvent("e='SaveChardump' ok=true")
 end
 
+function _M:orderSaveCharball(o)
+	self:command("CHAR", "CHARBALL", o.data:len(), o.uuid, o.module)
+	if not self:read("200") then return end
+	self.sock:send(o.data)
+	cprofile.pushEvent("e='SaveCharball' ok=true")
+end
+
 function _M:orderCurrentCharacter(o)
 	self:command("CHAR", "CUR", table.serialize(o))
 	self.cur_char = o