diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,10 @@
+## [v0.7.1.0, aka 'Ancient troubles'](https://github.com/LambdaHack/LambdaHack/compare/v0.7.0.0...v0.7.1.0)
+
+- add amazing cave and item (actor, blast, organ) descriptions
+- package for Windows as an installer and also as zip archives
+- fix a crash from SDL frontend under some OpenGL drivers (no thread-safety)
+- add WWW address to the Main Menu, for other sites that may run our JS blob
+
 ## [v0.7.0.0, aka 'The dice are cast'](https://github.com/LambdaHack/LambdaHack/compare/v0.6.2.0...v0.7.0.0)
 
 - decouple tile searching from tile alteration
diff --git a/CREDITS b/CREDITS
--- a/CREDITS
+++ b/CREDITS
@@ -5,6 +5,7 @@
 Mikolaj Konarski
 Tuukka Turto
 Veronika Romashkina
+@Peritract
 
 
 Fonts 16x16x.fon, 8x8x.fon and 8x8xb.fon are are taken from
diff --git a/Game/LambdaHack/Client/AI/PickActorM.hs b/Game/LambdaHack/Client/AI/PickActorM.hs
--- a/Game/LambdaHack/Client/AI/PickActorM.hs
+++ b/Game/LambdaHack/Client/AI/PickActorM.hs
@@ -85,7 +85,7 @@
               -- because he is likely to be still stuck.
               Just ((aid, b), tgt)
             Just aidToAvoid | aid /= aidToAvoid ->
-              -- Not an attempted leader stuck this turn/
+              -- Not an attempted leader stuck this turn.
               Just ((aid, b), tgt)
             _ -> Nothing
       oursTgtRaw <- mapM refresh ours
diff --git a/Game/LambdaHack/Client/ClientOptions.hs b/Game/LambdaHack/Client/ClientOptions.hs
--- a/Game/LambdaHack/Client/ClientOptions.hs
+++ b/Game/LambdaHack/Client/ClientOptions.hs
@@ -25,6 +25,8 @@
       -- ^ Font size to use for the main game window.
   , scolorIsBold      :: Maybe Bool
       -- ^ Whether to use bold attribute for colorful characters.
+  , slogPriority      :: Maybe Int
+      -- ^ How much to log (e.g., from SDL). 1 is all, 5 is errors, the default.
   , smaxFps           :: Maybe Int
       -- ^ Maximal frames per second.
       -- This is better low and fixed, to avoid jerkiness and delays
@@ -67,6 +69,7 @@
   , sdlFonSizeAdd = Nothing
   , sfontSize = Nothing
   , scolorIsBold = Nothing
+  , slogPriority = Nothing
   , smaxFps = Nothing
   , sdisableAutoYes = False
   , snoAnim = Nothing
diff --git a/Game/LambdaHack/Client/UI/DisplayAtomicM.hs b/Game/LambdaHack/Client/UI/DisplayAtomicM.hs
--- a/Game/LambdaHack/Client/UI/DisplayAtomicM.hs
+++ b/Game/LambdaHack/Client/UI/DisplayAtomicM.hs
@@ -758,7 +758,9 @@
   case (toSt, partingPart) of
     (Just status, Just pp) -> do
       isNoConfirms <- isNoConfirmsGame
-      go <- if isNoConfirms then return False else displaySpaceEsc ColorFull ""
+      go <- if isNoConfirms && fmap stOutcome toSt /= Just Camping
+            then return False
+            else displaySpaceEsc ColorFull ""
       when (side == fid) recordHistory
         -- we are going to exit or restart, so record and clear, but only once
       when go $ do
@@ -820,10 +822,12 @@
                     if go2 then viewItems pointer2 else return True
         go3 <- viewItems 2
         when go3 $ do
-          -- Show score for any UI client after any kind of game exit,
-          -- even though it is saved only for human UI clients at game over.
-          scoreSlides <- scoreToSlideshow total status
-          void $ getConfirms ColorFull [K.spaceKM, K.escKM] scoreSlides
+          unless isNoConfirms $ do
+            -- Show score for any UI client after any kind of game exit,
+            -- even though it is saved only for human UI clients at game over
+            -- (that is not a noConfirms or benchmark game).
+            scoreSlides <- scoreToSlideshow total status
+            void $ getConfirms ColorFull [K.spaceKM, K.escKM] scoreSlides
           -- The last prompt stays onscreen during shutdown, etc.
           promptAdd pp
           partingSlide <- reportToSlideshow [K.spaceKM, K.escKM]
diff --git a/Game/LambdaHack/Client/UI/Frontend/Dom.hs b/Game/LambdaHack/Client/UI/Frontend/Dom.hs
--- a/Game/LambdaHack/Client/UI/Frontend/Dom.hs
+++ b/Game/LambdaHack/Client/UI/Frontend/Dom.hs
@@ -94,7 +94,7 @@
   tableElem <- unsafeCastTo HTMLTableElement tableElemRaw
   appendChild_ divBlock tableElem
   scharStyle <- getStyle tableElem
-  -- Speed: http://www.w3.org/TR/CSS21/tables.html#fixed-table-layout
+  -- Speed: <http://www.w3.org/TR/CSS21/tables.html#fixed-table-layout>
   setProp scharStyle "table-layout" "fixed"
   setProp scharStyle "font-family" "lambdaHackFont"
   setProp scharStyle "font-size" $ tshow (fromJust sfontSize) <> "px"
@@ -187,7 +187,7 @@
         maybe (return ())
               (\key -> IO.liftIO $ saveKMP rf modifier key pointer) mkey
       saveMouse = do
-        -- https://hackage.haskell.org/package/ghcjs-dom-0.2.1.0/docs/GHCJS-DOM-EventM.html
+        -- <https://hackage.haskell.org/package/ghcjs-dom-0.2.1.0/docs/GHCJS-DOM-EventM.html>
         but <- mouseButton
         modifier <- readMod
         let key = case but of
diff --git a/Game/LambdaHack/Client/UI/Frontend/Sdl.hs b/Game/LambdaHack/Client/UI/Frontend/Sdl.hs
--- a/Game/LambdaHack/Client/UI/Frontend/Sdl.hs
+++ b/Game/LambdaHack/Client/UI/Frontend/Sdl.hs
@@ -4,7 +4,7 @@
 #ifdef EXPOSE_INTERNAL
     -- * Internal operations
   , FontAtlas, FrontendSession(..), startupFun, shutdown, forceShutdown
-  , display, displayNoLock, modTranslate, keyTranslate, colorToRGBA
+  , display, drawFrame, modTranslate, keyTranslate, colorToRGBA
 #endif
   ) where
 
@@ -13,7 +13,6 @@
 import Game.LambdaHack.Common.Prelude hiding (Alt)
 
 import           Control.Concurrent
-import           Control.Concurrent.Async
 import qualified Data.Char as Char
 import qualified Data.EnumMap.Strict as EM
 import           Data.IORef
@@ -28,6 +27,7 @@
 import qualified SDL
 import qualified SDL.Font as TTF
 import           SDL.Input.Keyboard.Codes
+import qualified SDL.Raw.Basic as SDL (logSetAllPriority)
 import qualified SDL.Vect as Vect
 
 import           Game.LambdaHack.Client.ClientOptions
@@ -50,7 +50,9 @@
   , stexture          :: IORef SDL.Texture
   , spreviousFrame    :: IORef SingleFrame
   , sforcedShutdown   :: IORef Bool
-  , sdisplayPermitted :: MVar Bool
+  , scontinueSdlLoop  :: IORef Bool
+  , sframeQueue       :: MVar SingleFrame
+  , sframeDrawn       :: MVar ()
   }
 
 -- | The name of the frontend.
@@ -60,14 +62,22 @@
 -- | Set up and start the main loop providing input and output.
 --
 -- Apparently some SDL backends are not thread-safe
--- (https://wiki.libsdl.org/FAQDevelopment),
--- so we stick to main thread.
+-- (<https://wiki.libsdl.org/FAQDevelopment>;
+-- "this should only be run in the thread that initialized the video subsystem,
+-- and for extra safety, you should consider only doing those things
+-- on the main thread in any case")
+-- so we stick to a single bound thread (but not to the main thread;
+-- enough is enough and at least in the case of OpenGL all bound threads
+-- are supposed to be as good as the main thread).
 startup :: ClientOptions -> IO RawFrontend
 startup soptions = startupBound $ startupFun soptions
 
 startupFun :: ClientOptions -> MVar RawFrontend -> IO ()
 startupFun soptions@ClientOptions{..} rfMVar = do
   SDL.initialize [SDL.InitVideo, SDL.InitEvents]
+  -- lowest: pattern SDL_LOG_PRIORITY_VERBOSE = (1) :: LogPriority
+  -- our default: pattern SDL_LOG_PRIORITY_ERROR = (5) :: LogPriority
+  SDL.logSetAllPriority $ toEnum $ fromMaybe 5 slogPriority
   let title = fromJust stitle
       fontFileName = T.unpack (fromJust sdlFontFile)
       fontFile | isRelative fontFileName = fromJust sfontDir </> fontFileName
@@ -86,7 +96,10 @@
       screenV2 = SDL.V2 (toEnum $ xsize * boxSize)
                         (toEnum $ ysize * boxSize)
       windowConfig = SDL.defaultWindow {SDL.windowInitialSize = screenV2}
-      rendererConfig = SDL.defaultRenderer {SDL.rendererTargetTexture = True}
+      rendererConfig = SDL.RendererConfig
+        { rendererType          = SDL.AcceleratedVSyncRenderer
+        , rendererTargetTexture = True
+        }
   swindow <- SDL.createWindow title windowConfig
   srenderer <- SDL.createRenderer swindow (-1) rendererConfig
   let initTexture = do
@@ -104,78 +117,46 @@
   stexture <- newIORef texture
   spreviousFrame <- newIORef blankSingleFrame
   sforcedShutdown <- newIORef False
-  sdisplayPermitted <- newMVar True
+  scontinueSdlLoop <- newIORef True
+  sframeQueue <- newEmptyMVar
+  sframeDrawn <- newEmptyMVar
   let sess = FrontendSession{..}
-  rf <- createRawFrontend (display soptions sess) (shutdown sess)
+  rf <- createRawFrontend (display sess) (shutdown sess)
   putMVar rfMVar rf
   let pointTranslate :: forall i. (Enum i) => Vect.Point Vect.V2 i -> Point
       pointTranslate (SDL.P (SDL.V2 x y)) =
         Point (fromEnum x `div` boxSize) (fromEnum y `div` boxSize)
       redraw = do
-        displayPermitted <- takeMVar sdisplayPermitted
-        when displayPermitted $ do
-          -- Textures may be trashed and even invalid, especially on Windows.
-          atlas <- readIORef satlas
-          writeIORef satlas EM.empty
-          oldTexture <- readIORef stexture
-          newTexture <- initTexture
-          mapM_ SDL.destroyTexture $ EM.elems atlas
-          SDL.destroyTexture oldTexture
-          writeIORef stexture newTexture
-          prevFrame <- readIORef spreviousFrame
-          writeIORef spreviousFrame blankSingleFrame
-          displayNoLock soptions sess prevFrame
-        putMVar sdisplayPermitted displayPermitted
-      storeKeys :: IO ()
-      storeKeys = do
-        e <- SDL.waitEvent  -- blocks here, so no polling
-        case SDL.eventPayload e of
-          SDL.KeyboardEvent keyboardEvent
-            | SDL.keyboardEventKeyMotion keyboardEvent == SDL.Pressed -> do
-              let sym = SDL.keyboardEventKeysym keyboardEvent
-                  ksm = SDL.keysymModifier sym
-                  shiftPressed = SDL.keyModifierLeftShift ksm
-                                 || SDL.keyModifierRightShift ksm
-                  key = keyTranslate shiftPressed $ SDL.keysymKeycode sym
-                  modifier = modTranslate ksm
-              p <- SDL.getAbsoluteMouseLocation
-              when (key == K.Esc) $ resetChanKey (fchanKey rf)
-              saveKMP rf modifier key (pointTranslate p)
-          SDL.MouseButtonEvent mouseButtonEvent
-            | SDL.mouseButtonEventMotion mouseButtonEvent == SDL.Released -> do
-              md <- modTranslate <$> SDL.getModState
-              let key = case SDL.mouseButtonEventButton mouseButtonEvent of
-                    SDL.ButtonLeft -> K.LeftButtonRelease
-                    SDL.ButtonMiddle -> K.MiddleButtonRelease
-                    SDL.ButtonRight -> K.RightButtonRelease
-                    _ -> K.LeftButtonRelease  -- any other is spare left
-                  modifier = if md == K.Shift then K.NoModifier else md
-                  p = SDL.mouseButtonEventPos mouseButtonEvent
-              saveKMP rf modifier key (pointTranslate p)
-          SDL.MouseWheelEvent mouseWheelEvent -> do
-            md <- modTranslate <$> SDL.getModState
-            let SDL.V2 _ y = SDL.mouseWheelEventPos mouseWheelEvent
-                mkey = case (compare y 0, SDL.mouseWheelEventDirection
-                                            mouseWheelEvent) of
-                  (EQ, _) -> Nothing
-                  (LT, SDL.ScrollNormal) -> Just K.WheelSouth
-                  (GT, SDL.ScrollNormal) -> Just K.WheelNorth
-                  (LT, SDL.ScrollFlipped) -> Just K.WheelSouth
-                  (GT, SDL.ScrollFlipped) -> Just K.WheelNorth
-                modifier = if md == K.Shift then K.NoModifier else md
-            p <- SDL.getAbsoluteMouseLocation
-            maybe (return ())
-                  (\key -> saveKMP rf modifier key (pointTranslate p)) mkey
-          SDL.WindowClosedEvent{} -> forceShutdown sess
-          SDL.QuitEvent -> forceShutdown sess
-          SDL.WindowRestoredEvent{} -> redraw
-          SDL.WindowExposedEvent{} -> redraw  -- needed on Windows
-          -- Probably not needed, because textures nor their content not lost:
-          -- SDL.WindowShownEvent{} -> redraw
-          _ -> return ()
-        displayPermitted <- readMVar sdisplayPermitted
-        if displayPermitted
-        then storeKeys
+        -- Textures may be trashed and even invalid, especially on Windows.
+        atlas <- readIORef satlas
+        writeIORef satlas EM.empty
+        oldTexture <- readIORef stexture
+        newTexture <- initTexture
+        mapM_ SDL.destroyTexture $ EM.elems atlas
+        SDL.destroyTexture oldTexture
+        writeIORef stexture newTexture
+        prevFrame <- readIORef spreviousFrame
+        writeIORef spreviousFrame blankSingleFrame
+        drawFrame soptions sess prevFrame
+      loopSDL :: IO ()
+      loopSDL = do
+        me <- SDL.pollEvent  -- events take precedence over frames
+        case me of
+          Nothing -> do
+            mfr <- tryTakeMVar sframeQueue
+            case mfr of
+              Just fr -> do
+                -- Some SDL2 (OpenGL) backends are very thread-unsafe,
+                -- so we need to ensure we draw on the same (bound) OS thread
+                -- that initialized SDL, hence we have to poll frames.
+                drawFrame soptions sess fr
+                putMVar sframeDrawn ()  -- signal that drawing ended
+              Nothing -> threadDelay 15000
+                           -- 60 polls per second, so keyboard snappy enough
+          Just e -> handleEvent e
+        continueSdlLoop <- readIORef scontinueSdlLoop
+        if continueSdlLoop
+        then loopSDL
         else do
           TTF.free sfont
           TTF.quit
@@ -185,10 +166,54 @@
           forcedShutdown <- readIORef sforcedShutdown
           when forcedShutdown
             exitSuccess  -- not in the main thread, so no exit yet, see "Main"
-  storeKeys
+      handleEvent e = case SDL.eventPayload e of
+        SDL.KeyboardEvent keyboardEvent
+          | SDL.keyboardEventKeyMotion keyboardEvent == SDL.Pressed -> do
+            let sym = SDL.keyboardEventKeysym keyboardEvent
+                ksm = SDL.keysymModifier sym
+                shiftPressed = SDL.keyModifierLeftShift ksm
+                               || SDL.keyModifierRightShift ksm
+                key = keyTranslate shiftPressed $ SDL.keysymKeycode sym
+                modifier = modTranslate ksm
+            p <- SDL.getAbsoluteMouseLocation
+            when (key == K.Esc) $ resetChanKey (fchanKey rf)
+            saveKMP rf modifier key (pointTranslate p)
+        SDL.MouseButtonEvent mouseButtonEvent
+          | SDL.mouseButtonEventMotion mouseButtonEvent == SDL.Released -> do
+            md <- modTranslate <$> SDL.getModState
+            let key = case SDL.mouseButtonEventButton mouseButtonEvent of
+                  SDL.ButtonLeft -> K.LeftButtonRelease
+                  SDL.ButtonMiddle -> K.MiddleButtonRelease
+                  SDL.ButtonRight -> K.RightButtonRelease
+                  _ -> K.LeftButtonRelease  -- any other is spare left
+                modifier = if md == K.Shift then K.NoModifier else md
+                p = SDL.mouseButtonEventPos mouseButtonEvent
+            saveKMP rf modifier key (pointTranslate p)
+        SDL.MouseWheelEvent mouseWheelEvent -> do
+          md <- modTranslate <$> SDL.getModState
+          let SDL.V2 _ y = SDL.mouseWheelEventPos mouseWheelEvent
+              mkey = case (compare y 0, SDL.mouseWheelEventDirection
+                                          mouseWheelEvent) of
+                (EQ, _) -> Nothing
+                (LT, SDL.ScrollNormal) -> Just K.WheelSouth
+                (GT, SDL.ScrollNormal) -> Just K.WheelNorth
+                (LT, SDL.ScrollFlipped) -> Just K.WheelSouth
+                (GT, SDL.ScrollFlipped) -> Just K.WheelNorth
+              modifier = if md == K.Shift then K.NoModifier else md
+          p <- SDL.getAbsoluteMouseLocation
+          maybe (return ())
+                (\key -> saveKMP rf modifier key (pointTranslate p)) mkey
+        SDL.WindowClosedEvent{} -> forceShutdown sess
+        SDL.QuitEvent -> forceShutdown sess
+        SDL.WindowRestoredEvent{} -> redraw
+        SDL.WindowExposedEvent{} -> redraw  -- needed on Windows
+        -- Probably not needed, because textures nor their content not lost:
+        -- SDL.WindowShownEvent{} -> redraw
+        _ -> return ()
+  loopSDL
 
 shutdown :: FrontendSession -> IO ()
-shutdown FrontendSession{..} = void $ swapMVar sdisplayPermitted False
+shutdown FrontendSession{..} = writeIORef scontinueSdlLoop False
 
 forceShutdown :: FrontendSession -> IO ()
 forceShutdown sess@FrontendSession{..} = do
@@ -196,33 +221,31 @@
   shutdown sess
 
 -- | Add a frame to be drawn.
-display :: ClientOptions
-        -> FrontendSession  -- ^ frontend session data
+display :: FrontendSession  -- ^ frontend session data
         -> SingleFrame      -- ^ the screen frame to draw
         -> IO ()
-display soptions sess@FrontendSession{..} curFrame = do
-  displayPermitted <- takeMVar sdisplayPermitted
-  if displayPermitted then do
-    -- Apparently some SDL backends are not thread-safe, so keep to main thread:
-    a <- asyncBound $ displayNoLock soptions sess curFrame
-    wait a
-    putMVar sdisplayPermitted displayPermitted
+display FrontendSession{..} curFrame = do
+  continueSdlLoop <- readIORef scontinueSdlLoop
+  if continueSdlLoop then do
+    putMVar sframeQueue curFrame
+    -- Wait until the frame is drawn.
+    takeMVar sframeDrawn
   else do
-    putMVar sdisplayPermitted displayPermitted
     forcedShutdown <- readIORef sforcedShutdown
     when forcedShutdown $
       -- When there's a forced shutdown, ignore displaying one frame
       -- and don't occupy the CPU creating new ones and moving on with the game
       -- (possibly also saving the new game state, surprising the player),
-      -- but give time for SDL to clean up and exit via @exitSuccess@
+      -- but delay the server and client thread(s) for a long time
+      -- and let the SDL-init thread clean up and exit via @exitSuccess@
       -- to avoid exiting via "thread blocked".
       threadDelay 50000
 
-displayNoLock :: ClientOptions
-              -> FrontendSession  -- ^ frontend session data
-              -> SingleFrame      -- ^ the screen frame to draw
-              -> IO ()
-displayNoLock ClientOptions{..} FrontendSession{..} curFrame = do
+drawFrame :: ClientOptions
+          -> FrontendSession  -- ^ frontend session data
+          -> SingleFrame      -- ^ the screen frame to draw
+          -> IO ()
+drawFrame ClientOptions{..} FrontendSession{..} curFrame = do
   let isFonFile = "fon" `isSuffixOf` T.unpack (fromJust sdlFontFile)
       sdlSizeAdd = fromJust $ if isFonFile then sdlFonSizeAdd else sdlTtfSizeAdd
   boxSize <- (+ sdlSizeAdd) <$> TTF.height sfont
@@ -234,7 +257,8 @@
         let rect = SDL.Rectangle (vp (x * boxSize) (y * boxSize))
                                  (Vect.V2 (toEnum boxSize) (toEnum boxSize))
         SDL.drawRect srenderer $ Just rect
-        SDL.rendererDrawColor srenderer SDL.$= colorToRGBA Color.Black  -- reset back to black
+        SDL.rendererDrawColor srenderer SDL.$= colorToRGBA Color.Black
+          -- reset back to black
       setChar :: Int -> Word32 -> Word32 -> IO ()
       setChar i w wPrev = unless (w == wPrev) $ do
         atlas <- readIORef satlas
@@ -251,7 +275,7 @@
               Color.HighlightGrey -> normalizeAc Color.BrBlack
               Color.HighlightWhite -> normalizeAc Color.White
               Color.HighlightMagenta -> normalizeAc Color.BrMagenta
-        -- https://www.libsdl.org/projects/SDL_ttf/docs/SDL_ttf_42.html#SEC42
+        -- <https://www.libsdl.org/projects/SDL_ttf/docs/SDL_ttf_42.html#SEC42>
         textTexture <- case EM.lookup ac atlas of
           Nothing -> do
             -- Make all visible floors bold (no bold fold variant for 16x16x,
@@ -264,7 +288,8 @@
                                          else 8901  -- 0x22c5
                          else acCharRaw
             textSurface <-
-              TTF.shadedGlyph sfont (colorToRGBA fg) (colorToRGBA Color.Black) acChar
+              TTF.shadedGlyph sfont (colorToRGBA fg)
+                                    (colorToRGBA Color.Black) acChar
             textTexture <- SDL.createTextureFromSurface srenderer textSurface
             SDL.freeSurface textSurface
             writeIORef satlas $ EM.insert ac textTexture atlas  -- not @acRaw@
@@ -309,107 +334,106 @@
     False
 
 keyTranslate :: Bool -> SDL.Keycode -> K.Key
-keyTranslate shiftPressed n =
-  case n of
-    KeycodeEscape     -> K.Esc
-    KeycodeReturn     -> K.Return
-    KeycodeBackspace  -> K.BackSpace
-    KeycodeTab        -> if shiftPressed then K.BackTab else K.Tab
-    KeycodeSpace      -> K.Space
-    KeycodeExclaim -> K.Char '!'
-    KeycodeQuoteDbl -> K.Char '"'
-    KeycodeHash -> K.Char '#'
-    KeycodePercent -> K.Char '%'
-    KeycodeDollar -> K.Char '$'
-    KeycodeAmpersand -> K.Char '&'
-    KeycodeQuote -> if shiftPressed then K.Char '"' else K.Char '\''
-    KeycodeLeftParen -> K.Char '('
-    KeycodeRightParen -> K.Char ')'
-    KeycodeAsterisk -> K.Char '*'
-    KeycodePlus -> K.Char '+'
-    KeycodeComma -> if shiftPressed then K.Char '<' else K.Char ','
-    KeycodeMinus -> if shiftPressed then K.Char '_' else K.Char '-'
-    KeycodePeriod -> if shiftPressed then K.Char '>' else K.Char '.'
-    KeycodeSlash -> if shiftPressed then K.Char '?' else K.Char '/'
-    Keycode1 -> if shiftPressed then K.Char '!' else K.Char '1'
-    Keycode2 -> if shiftPressed then K.Char '@' else K.Char '2'
-    Keycode3 -> if shiftPressed then K.Char '#' else K.Char '3'
-    Keycode4 -> if shiftPressed then K.Char '$' else K.Char '4'
-    Keycode5 -> if shiftPressed then K.Char '%' else K.Char '5'
-    Keycode6 -> if shiftPressed then K.Char '^' else K.Char '6'
-    Keycode7 -> if shiftPressed then K.Char '&' else K.Char '7'
-    Keycode8 -> if shiftPressed then K.Char '*' else K.Char '8'
-    Keycode9 -> if shiftPressed then K.Char '(' else K.Char '9'
-    Keycode0 -> if shiftPressed then K.Char ')' else K.Char '0'
-    KeycodeColon -> K.Char ':'
-    KeycodeSemicolon -> if shiftPressed then K.Char ':' else K.Char ';'
-    KeycodeLess -> K.Char '<'
-    KeycodeEquals -> if shiftPressed then K.Char '+' else K.Char '='
-    KeycodeGreater -> K.Char '>'
-    KeycodeQuestion -> K.Char '?'
-    KeycodeAt -> K.Char '@'
-    KeycodeLeftBracket -> if shiftPressed then K.Char '{' else K.Char '['
-    KeycodeBackslash -> if shiftPressed then K.Char '|' else K.Char '\\'
-    KeycodeRightBracket -> if shiftPressed then K.Char '}' else K.Char ']'
-    KeycodeCaret -> K.Char '^'
-    KeycodeUnderscore -> K.Char '_'
-    KeycodeBackquote -> if shiftPressed then K.Char '~' else K.Char '`'
-    KeycodeUp         -> K.Up
-    KeycodeDown       -> K.Down
-    KeycodeLeft       -> K.Left
-    KeycodeRight      -> K.Right
-    KeycodeHome       -> K.Home
-    KeycodeEnd        -> K.End
-    KeycodePageUp     -> K.PgUp
-    KeycodePageDown   -> K.PgDn
-    KeycodeInsert     -> K.Insert
-    KeycodeDelete     -> K.Delete
-    KeycodeKPDivide   -> K.KP '/'
-    KeycodeKPMultiply -> K.KP '*'
-    KeycodeKPMinus    -> K.Char '-'  -- KP and normal are merged here
-    KeycodeKPPlus     -> K.Char '+'  -- KP and normal are merged here
-    KeycodeKPEnter    -> K.Return
-    KeycodeKPEquals   -> K.Return  -- in case of some funny layouts
-    KeycodeKP1 -> if shiftPressed then K.KP '1' else K.End
-    KeycodeKP2 -> if shiftPressed then K.KP '2' else K.Down
-    KeycodeKP3 -> if shiftPressed then K.KP '3' else K.PgDn
-    KeycodeKP4 -> if shiftPressed then K.KP '4' else K.Left
-    KeycodeKP5 -> if shiftPressed then K.KP '5' else K.Begin
-    KeycodeKP6 -> if shiftPressed then K.KP '6' else K.Right
-    KeycodeKP7 -> if shiftPressed then K.KP '7' else K.Home
-    KeycodeKP8 -> if shiftPressed then K.KP '8' else K.Up
-    KeycodeKP9 -> if shiftPressed then K.KP '9' else K.PgUp
-    KeycodeKP0 -> if shiftPressed then K.KP '0' else K.Insert
-    KeycodeKPPeriod -> K.Char '.'  -- dot and comma are merged here
-    KeycodeKPComma  -> K.Char '.'  -- to sidestep national standards
-    KeycodeF1       -> K.Fun 1
-    KeycodeF2       -> K.Fun 2
-    KeycodeF3       -> K.Fun 3
-    KeycodeF4       -> K.Fun 4
-    KeycodeF5       -> K.Fun 5
-    KeycodeF6       -> K.Fun 6
-    KeycodeF7       -> K.Fun 7
-    KeycodeF8       -> K.Fun 8
-    KeycodeF9       -> K.Fun 9
-    KeycodeF10      -> K.Fun 10
-    KeycodeF11      -> K.Fun 11
-    KeycodeF12      -> K.Fun 12
-    KeycodeLCtrl    -> K.DeadKey
-    KeycodeLShift   -> K.DeadKey
-    KeycodeLAlt     -> K.DeadKey
-    KeycodeLGUI     -> K.DeadKey
-    KeycodeRCtrl    -> K.DeadKey
-    KeycodeRShift   -> K.DeadKey
-    KeycodeRAlt     -> K.DeadKey
-    KeycodeRGUI     -> K.DeadKey
-    KeycodeMode     -> K.DeadKey
-    KeycodeNumLockClear -> K.DeadKey
-    KeycodeUnknown  -> K.Unknown "KeycodeUnknown"
-    _ -> let i = fromEnum $ unwrapKeycode n
-         in if | 97 <= i && i <= 122
-                 && shiftPressed -> K.Char $ Char.chr $ i - 32
-               | 32 <= i && i <= 126 -> K.Char $ Char.chr i
-               | otherwise -> K.Unknown $ show n
+keyTranslate shiftPressed n = case n of
+  KeycodeEscape     -> K.Esc
+  KeycodeReturn     -> K.Return
+  KeycodeBackspace  -> K.BackSpace
+  KeycodeTab        -> if shiftPressed then K.BackTab else K.Tab
+  KeycodeSpace      -> K.Space
+  KeycodeExclaim -> K.Char '!'
+  KeycodeQuoteDbl -> K.Char '"'
+  KeycodeHash -> K.Char '#'
+  KeycodePercent -> K.Char '%'
+  KeycodeDollar -> K.Char '$'
+  KeycodeAmpersand -> K.Char '&'
+  KeycodeQuote -> if shiftPressed then K.Char '"' else K.Char '\''
+  KeycodeLeftParen -> K.Char '('
+  KeycodeRightParen -> K.Char ')'
+  KeycodeAsterisk -> K.Char '*'
+  KeycodePlus -> K.Char '+'
+  KeycodeComma -> if shiftPressed then K.Char '<' else K.Char ','
+  KeycodeMinus -> if shiftPressed then K.Char '_' else K.Char '-'
+  KeycodePeriod -> if shiftPressed then K.Char '>' else K.Char '.'
+  KeycodeSlash -> if shiftPressed then K.Char '?' else K.Char '/'
+  Keycode1 -> if shiftPressed then K.Char '!' else K.Char '1'
+  Keycode2 -> if shiftPressed then K.Char '@' else K.Char '2'
+  Keycode3 -> if shiftPressed then K.Char '#' else K.Char '3'
+  Keycode4 -> if shiftPressed then K.Char '$' else K.Char '4'
+  Keycode5 -> if shiftPressed then K.Char '%' else K.Char '5'
+  Keycode6 -> if shiftPressed then K.Char '^' else K.Char '6'
+  Keycode7 -> if shiftPressed then K.Char '&' else K.Char '7'
+  Keycode8 -> if shiftPressed then K.Char '*' else K.Char '8'
+  Keycode9 -> if shiftPressed then K.Char '(' else K.Char '9'
+  Keycode0 -> if shiftPressed then K.Char ')' else K.Char '0'
+  KeycodeColon -> K.Char ':'
+  KeycodeSemicolon -> if shiftPressed then K.Char ':' else K.Char ';'
+  KeycodeLess -> K.Char '<'
+  KeycodeEquals -> if shiftPressed then K.Char '+' else K.Char '='
+  KeycodeGreater -> K.Char '>'
+  KeycodeQuestion -> K.Char '?'
+  KeycodeAt -> K.Char '@'
+  KeycodeLeftBracket -> if shiftPressed then K.Char '{' else K.Char '['
+  KeycodeBackslash -> if shiftPressed then K.Char '|' else K.Char '\\'
+  KeycodeRightBracket -> if shiftPressed then K.Char '}' else K.Char ']'
+  KeycodeCaret -> K.Char '^'
+  KeycodeUnderscore -> K.Char '_'
+  KeycodeBackquote -> if shiftPressed then K.Char '~' else K.Char '`'
+  KeycodeUp         -> K.Up
+  KeycodeDown       -> K.Down
+  KeycodeLeft       -> K.Left
+  KeycodeRight      -> K.Right
+  KeycodeHome       -> K.Home
+  KeycodeEnd        -> K.End
+  KeycodePageUp     -> K.PgUp
+  KeycodePageDown   -> K.PgDn
+  KeycodeInsert     -> K.Insert
+  KeycodeDelete     -> K.Delete
+  KeycodeKPDivide   -> K.KP '/'
+  KeycodeKPMultiply -> K.KP '*'
+  KeycodeKPMinus    -> K.Char '-'  -- KP and normal are merged here
+  KeycodeKPPlus     -> K.Char '+'  -- KP and normal are merged here
+  KeycodeKPEnter    -> K.Return
+  KeycodeKPEquals   -> K.Return  -- in case of some funny layouts
+  KeycodeKP1 -> if shiftPressed then K.KP '1' else K.End
+  KeycodeKP2 -> if shiftPressed then K.KP '2' else K.Down
+  KeycodeKP3 -> if shiftPressed then K.KP '3' else K.PgDn
+  KeycodeKP4 -> if shiftPressed then K.KP '4' else K.Left
+  KeycodeKP5 -> if shiftPressed then K.KP '5' else K.Begin
+  KeycodeKP6 -> if shiftPressed then K.KP '6' else K.Right
+  KeycodeKP7 -> if shiftPressed then K.KP '7' else K.Home
+  KeycodeKP8 -> if shiftPressed then K.KP '8' else K.Up
+  KeycodeKP9 -> if shiftPressed then K.KP '9' else K.PgUp
+  KeycodeKP0 -> if shiftPressed then K.KP '0' else K.Insert
+  KeycodeKPPeriod -> K.Char '.'  -- dot and comma are merged here
+  KeycodeKPComma  -> K.Char '.'  -- to sidestep national standards
+  KeycodeF1       -> K.Fun 1
+  KeycodeF2       -> K.Fun 2
+  KeycodeF3       -> K.Fun 3
+  KeycodeF4       -> K.Fun 4
+  KeycodeF5       -> K.Fun 5
+  KeycodeF6       -> K.Fun 6
+  KeycodeF7       -> K.Fun 7
+  KeycodeF8       -> K.Fun 8
+  KeycodeF9       -> K.Fun 9
+  KeycodeF10      -> K.Fun 10
+  KeycodeF11      -> K.Fun 11
+  KeycodeF12      -> K.Fun 12
+  KeycodeLCtrl    -> K.DeadKey
+  KeycodeLShift   -> K.DeadKey
+  KeycodeLAlt     -> K.DeadKey
+  KeycodeLGUI     -> K.DeadKey
+  KeycodeRCtrl    -> K.DeadKey
+  KeycodeRShift   -> K.DeadKey
+  KeycodeRAlt     -> K.DeadKey
+  KeycodeRGUI     -> K.DeadKey
+  KeycodeMode     -> K.DeadKey
+  KeycodeNumLockClear -> K.DeadKey
+  KeycodeUnknown  -> K.Unknown "KeycodeUnknown"
+  _ -> let i = fromEnum $ unwrapKeycode n
+       in if | 97 <= i && i <= 122
+               && shiftPressed -> K.Char $ Char.chr $ i - 32
+             | 32 <= i && i <= 126 -> K.Char $ Char.chr i
+             | otherwise -> K.Unknown $ show n
 
 
 sDL_ALPHA_OPAQUE :: Word8
diff --git a/Game/LambdaHack/Client/UI/HandleHumanLocalM.hs b/Game/LambdaHack/Client/UI/HandleHumanLocalM.hs
--- a/Game/LambdaHack/Client/UI/HandleHumanLocalM.hs
+++ b/Game/LambdaHack/Client/UI/HandleHumanLocalM.hs
@@ -755,6 +755,7 @@
                       in "Originally of" <+> gname tfact
                          <> ", now fighting for" <+> dominatedBy <> "."
                     _ | bfid body == side -> ""  -- just one of us
+                    _ | bproj body -> "Launched by" <+> gname bfact <> "."
                     _ -> "One of" <+> gname bfact <> "."
                   idesc = case itemDisco $ itemToF (btrunk body) (1, []) of
                     Nothing -> ""  -- no details, only show the name
diff --git a/Game/LambdaHack/Client/UI/Key.hs b/Game/LambdaHack/Client/UI/Key.hs
--- a/Game/LambdaHack/Client/UI/Key.hs
+++ b/Game/LambdaHack/Client/UI/Key.hs
@@ -281,7 +281,7 @@
 -- To be used, in particular, for the command bindings and macros
 -- in the config file.
 --
--- See https://github.com/twobob/gtk-/blob/master/gdk/keyname-table.h
+-- See <https://github.com/twobob/gtk-/blob/master/gdk/keyname-table.h>
 keyTranslate :: String -> Key
 keyTranslate "less"          = Char '<'
 keyTranslate "greater"       = Char '>'
@@ -426,7 +426,7 @@
 keyTranslate s               = Unknown s
 
 -- | Translate key from a Web API string description
--- (https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key#Key_values)
+-- (<https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key#Key_values>)
 -- to our internal key type. To be used in web frontends.
 -- The argument says whether Shift is pressed.
 keyTranslateWeb :: String -> Bool -> Key
diff --git a/Game/LambdaHack/Common/Save.hs b/Game/LambdaHack/Common/Save.hs
--- a/Game/LambdaHack/Common/Save.hs
+++ b/Game/LambdaHack/Common/Save.hs
@@ -52,6 +52,7 @@
         dataDir <- appDataDir
         tryCreateDir (dataDir </> "saves")
         let fileName = stateToFileName s
+        yield  -- minimize UI lag due to saving
         encodeEOF (dataDir </> "saves" </> fileName) (vExevLib cops, s)
         -- Wait until the save finished. During that time, the mvar
         -- is continually updated to newest state values.
diff --git a/Game/LambdaHack/Common/Thread.hs b/Game/LambdaHack/Common/Thread.hs
--- a/Game/LambdaHack/Common/Thread.hs
+++ b/Game/LambdaHack/Common/Thread.hs
@@ -10,7 +10,8 @@
 import Control.Concurrent.Async
 import Control.Concurrent.MVar
 
--- Swiped from http://www.haskell.org/ghc/docs/latest/html/libraries/base/Control-Concurrent.html. Ported to Async to link exceptions, to let travis tests fail.
+-- Swiped from <http://www.haskell.org/ghc/docs/latest/html/libraries/base/Control-Concurrent.html>
+-- Ported to Async to link exceptions, to let travis tests fail.
 
 forkChild :: MVar [Async ()] -> IO () -> IO ()
 forkChild children io = do
diff --git a/Game/LambdaHack/Server/Commandline.hs b/Game/LambdaHack/Server/Commandline.hs
--- a/Game/LambdaHack/Server/Commandline.hs
+++ b/Game/LambdaHack/Server/Commandline.hs
@@ -12,8 +12,11 @@
 import Prelude ()
 
 import Game.LambdaHack.Common.Prelude
+-- Cabal
+import qualified Paths_LambdaHack as Self (version)
 
 import qualified Data.Text as T
+import           Data.Version
 import           Options.Applicative
 import qualified System.Random as R
 
@@ -29,10 +32,15 @@
 
 -- | Parser for server options from commandline arguments.
 serverOptionsPI :: ParserInfo ServerOptions
-serverOptionsPI =  info ( helper <*> serverOptionsP )
+serverOptionsPI =  info (serverOptionsP <**> helper <**> version)
                $  fullDesc
                <> progDesc "Configure debug options here, gameplay options in configuration file."
 
+version :: Parser (a -> a)
+version = infoOption (showVersion Self.version)
+  (  long "version"
+  <> help "Print engine version information" )
+
 serverOptionsP :: Parser ServerOptions
 serverOptionsP = do
   ~(snewGameSer, scurChalSer)
@@ -60,6 +68,7 @@
   sfontSize         <- fontSizeP
   sfontDir          <- fontDirP
   scolorIsBold      <- noColorIsBoldP
+  slogPriority      <- logPriorityP
   smaxFps           <- maxFpsP
   sdisableAutoYes   <- disableAutoYesP
   snoAnim           <- noAnimP
@@ -228,6 +237,12 @@
   option auto (  long "maxFps"
               <> metavar "N"
               <> help "Display at most N frames per second" )
+
+logPriorityP :: Parser (Maybe Int)
+logPriorityP = optional $ max 1 <$>
+  option auto (  long "logPriority"
+              <> metavar "N"
+              <> help "Log only messages of priority at least N, where 1 (all) is the lowest and 5 (errors only) is the default." )
 
 disableAutoYesP :: Parser Bool
 disableAutoYesP =
diff --git a/Game/LambdaHack/Server/DebugM.hs b/Game/LambdaHack/Server/DebugM.hs
--- a/Game/LambdaHack/Server/DebugM.hs
+++ b/Game/LambdaHack/Server/DebugM.hs
@@ -40,6 +40,7 @@
 debugResponse fid resp = case resp of
   RespUpdAtomic _ cmd@UpdPerception{} -> debugPlain fid "RespUpdAtomic" cmd
   RespUpdAtomic _ cmd@UpdResume{} -> debugPlain fid "RespUpdAtomic" cmd
+  RespUpdAtomic _ cmd@UpdRestart{} -> debugPlain fid "RespUpdAtomic" cmd
   RespUpdAtomic _ cmd@UpdSpotTile{} -> debugPlain fid "RespUpdAtomic" cmd
   RespUpdAtomic _ cmd -> debugPretty fid "RespUpdAtomic" cmd
   RespUpdAtomicNoState cmd@UpdPerception{} ->
diff --git a/Game/LambdaHack/Server/HandleRequestM.hs b/Game/LambdaHack/Server/HandleRequestM.hs
--- a/Game/LambdaHack/Server/HandleRequestM.hs
+++ b/Game/LambdaHack/Server/HandleRequestM.hs
@@ -85,7 +85,7 @@
 {-# INLINE setBWait #-}
 setBWait cmd aid b = do
   let mwait = case cmd of
-        ReqWait -> Just True  -- true wait, with bracind, no overhead, etc.
+        ReqWait -> Just True  -- true wait, with bracing, no overhead, etc.
         ReqWait10 -> Just False  -- false wait, only one clip at a time
         _ -> Nothing
   when ((mwait == Just True) /= bwait b) $
diff --git a/Game/LambdaHack/Server/PeriodicM.hs b/Game/LambdaHack/Server/PeriodicM.hs
--- a/Game/LambdaHack/Server/PeriodicM.hs
+++ b/Game/LambdaHack/Server/PeriodicM.hs
@@ -160,13 +160,13 @@
   modifyServer $ \ser ->
     ser {sactorTime = ageActor (bfid b) (blid b) aid t $ sactorTime ser}
 
--- Add communication overhead time delta to all non-projectile, non-dying
--- faction's actors (except the leader). Effectively, this limits moves
+-- | Add communication overhead time delta to all non-projectile, non-dying
+-- faction's actors, except the leader. Effectively, this limits moves
 -- of a faction on a level to 10, regardless of the number of actors
 -- and their speeds. To avoid animals suddenly acting extremely sluggish
 -- whenever monster's leader visits a distant arena that has a crowd
 -- of animals, overhead applies only to actors on the same level.
--- Since the number of active arenas is limited, this bounds the total moves
+-- Since the number of active levels is limited, this bounds the total moves
 -- per turn of each faction as well.
 --
 -- Leader is immune from overhead and so he is faster than other faction
@@ -176,22 +176,21 @@
 -- having very long UI turns, introducing UI lag.
 overheadActorTime :: MonadServerAtomic m => FactionId -> LevelId -> m ()
 overheadActorTime fid lid = do
-  actorTime <- getsServer $ (EM.! fid) . sactorTime
+  actorTimeFid <- getsServer $ (EM.! fid) . sactorTime
+  let actorTimeLid = actorTimeFid EM.! lid
   s <- getState
   mleader <- getsState $ gleader . (EM.! fid) . sfactionD
-  arenas <- getsServer sarenas
-  let f !lid2 !_aid !time | lid2 /= lid = time
-      f _ aid time =
+  let f !aid !time =
         let body = getActorBody aid s
         in if isNothing (btrajectory body)
-              && bhp body > 0
+              && bhp body > 0  -- speed up all-move-at-once carcass removal
               && Just aid /= mleader  -- leader fast, for UI to be fast
            then timeShift time (Delta timeClip)
            else time
-      g !acc !lid2 = EM.adjust (EM.mapWithKey $ f lid2) lid acc
-      actorTimeNew = foldl' g actorTime arenas
+      actorTimeLid2 = EM.mapWithKey f actorTimeLid
+      actorTimeFid2 = EM.insert lid actorTimeLid2 actorTimeFid
   modifyServer $ \ser ->
-    ser {sactorTime = EM.insert fid actorTimeNew $ sactorTime ser}
+    ser {sactorTime = EM.insert fid actorTimeFid2 $ sactorTime ser}
 
 -- | Swap the relative move times of two actors (e.g., when switching
 -- a UI leader).
diff --git a/GameDefinition/Content/CaveKind.hs b/GameDefinition/Content/CaveKind.hs
--- a/GameDefinition/Content/CaveKind.hs
+++ b/GameDefinition/Content/CaveKind.hs
@@ -60,7 +60,7 @@
   , clegendLitTile  = "legendLit"
   , cescapeGroup    = Nothing
   , cstairFreq      = [("staircase", 100)]
-  , cdesc         = ""
+  , cdesc         = "Winding tunnels stretch into the dark."
   }  -- no lit corridor alternative, because both lit # and . look bad here
 arena = rogue
   { csymbol       = 'A'
@@ -87,7 +87,7 @@
   , cdefTile      = "arenaSetLit"
   , cdarkCorTile  = "trailLit"  -- let trails give off light
   , clitCorTile   = "trailLit"
-  , cdesc         = ""
+  , cdesc         = "The shelves groan with dusty books and tattered scrolls."
   }
 arena2 = arena
   { cname         = "Smoking rooms"
@@ -98,7 +98,7 @@
   , citemNum      = 7 `d` 5  -- rare, so make it exciting
   , citemFreq     = [("useful", 20), ("treasure", 40), ("any vial", 40)]
   , cdefTile      = "arenaSetDark"
-  , cdesc         = ""
+  , cdesc         = "Velvet couches exude the strong smell of tobacco."
   }
 laboratory = arena2
   { csymbol       = 'L'
@@ -121,7 +121,7 @@
   , cdefTile      = "fillerWall"
   , cdarkCorTile  = "labTrailLit"  -- let lab smoke give off light always
   , clitCorTile   = "labTrailLit"
-  , cdesc         = "An experiment (or was it manufacturing?) had gone wrong here."
+  , cdesc         = "Shattered glassware and the sharp scent of spilt chemicals show that something terrible happened here."
   }
 empty = rogue
   { csymbol       = 'E'
@@ -153,7 +153,7 @@
   , cdefTile      = "emptySet"
   , cdarkCorTile  = "floorArenaDark"
   , clitCorTile   = "floorArenaLit"
-  , cdesc         = ""
+  , cdesc         = "Swirls of warm fog fill the air, the hiss of geysers sounding all around."
   }
 noise = rogue
   { csymbol       = 'N'
@@ -180,7 +180,7 @@
   , couterFenceTile = "noise fence"  -- ensures no cut-off parts from collapsed
   , cdarkCorTile  = "floorArenaDark"
   , clitCorTile   = "floorArenaLit"
-  , cdesc         = ""
+  , cdesc         = "Soon, these passages will be swallowed up by the mud."
   }
 noise2 = noise
   { cname         = "Frozen derelict mine"
@@ -189,12 +189,12 @@
   , citemNum      = 13 `d` 5  -- an incentive to explore the final labyrinth
   , cplaceFreq    = [("noise", 1), ("mine", 99)]
   , cstairFreq    = [("gated staircase", 100)]
-  , cdesc         = ""
+  , cdesc         = "Pillars of shining ice create a frozen labyrinth."
   }
 shallow2rogue = rogue
   { cfreq         = [("shallow random 2", 100)]
   , cextraStairs  = 1  -- ensure heroes meet initial monsters and their loot
-  , cdesc         = ""
+  , cdesc         = "The snorts and grunts of savage beasts can be clearly heard."
   }
 shallow1rogue = shallow2rogue
   { csymbol       = 'B'
@@ -206,7 +206,7 @@
   , citemNum      = 8 `d` 5  -- lure them in with loot
   , citemFreq     = filter ((/= "treasure") . fst) $ citemFreq rogue
   , cescapeGroup  = Just "escape up"
-  , cdesc         = ""
+  , cdesc         = "This close to the surface, the sunlight still illuminates the dungeon."
   }
 raid = rogue
   { csymbol       = 'T'
@@ -244,7 +244,7 @@
   , cdefTile      = "brawlSetLit"
   , cdarkCorTile  = "floorArenaLit"
   , clitCorTile   = "floorArenaLit"
-  , cdesc         = ""
+  , cdesc         = "Sunlight falls through the trees and dapples on the ground."
   }
 shootout = rogue  -- a scenario with strong missiles;
                   -- few solid tiles, but only translucent tiles or walkable
diff --git a/GameDefinition/Content/ItemKind.hs b/GameDefinition/Content/ItemKind.hs
--- a/GameDefinition/Content/ItemKind.hs
+++ b/GameDefinition/Content/ItemKind.hs
@@ -35,9 +35,9 @@
 
 items :: [ItemKind]
 items =
-  [sandstoneRock, dart, spike, slingStone, slingBullet, paralizingProj, harpoon, net, light1, light2, light3, blanket, flask1, flask2, flask3, flask4, flask5, flask6, flask7, flask8, flask9, flask10, flask11, flask12, flask13, flask14, flask15, flask16, flask17, flask18, flask19, flask20, potion1, potion2, potion3, potion4, potion5, potion6, potion7, potion8, potion9, potion10, scroll1, scroll2, scroll3, scroll4, scroll5, scroll6, scroll7, scroll8, scroll9, scroll10, scroll11, scroll12, scroll13, jumpingPole, sharpeningTool, seeingItem, motionScanner, gorget, necklace1, necklace2, necklace3, necklace4, necklace5, necklace6, necklace7, necklace8, necklace9, imageItensifier, sightSharpening, ring1, ring2, ring3, ring4, ring5, ring6, ring7, ring8, armorLeather, armorMail, gloveFencing, gloveGauntlet, gloveJousting, buckler, shield, dagger, daggerDropBestWeapon, hammer, hammerParalyze, hammerSpark, sword, swordImpress, swordNullify, halberd, halberdPushActor, wand1, wand2, gem1, gem2, gem3, gem4, gem5, currency]
+  [sandstoneRock, dart, spike, slingStone, slingBullet, paralizingProj, harpoon, net, light1, light2, light3, blanket, flask1, flask2, flask3, flask4, flask5, flask6, flask7, flask8, flask9, flask10, flask11, flask12, flask13, flask14, flask15, flask16, flask17, flask18, flask19, flask20, potion1, potion2, potion3, potion4, potion5, potion6, potion7, potion8, potion9, potion10, scroll1, scroll2, scroll3, scroll4, scroll5, scroll6, scroll7, scroll8, scroll9, scroll10, scroll11, scroll12, scroll13, jumpingPole, sharpeningTool, seeingItem, motionScanner, gorget, necklace1, necklace2, necklace3, necklace4, necklace5, necklace6, necklace7, necklace8, necklace9, imageItensifier, sightSharpening, ring1, ring2, ring3, ring4, ring5, ring6, ring7, ring8, armorLeather, armorMail, gloveFencing, gloveGauntlet, gloveJousting, buckler, shield, dagger, daggerDropBestWeapon, hammer, hammerParalyze, hammerSpark, sword, swordImpress, swordNullify, halberd, halberdPushActor, wand1, wand2, gem1, gem2, gem3, gem4, gem5, currency, explosive, firecracker]
 
-sandstoneRock,    dart, spike, slingStone, slingBullet, paralizingProj, harpoon, net, light1, light2, light3, blanket, flask1, flask2, flask3, flask4, flask5, flask6, flask7, flask8, flask9, flask10, flask11, flask12, flask13, flask14, flask15, flask16, flask17, flask18, flask19, flask20, potion1, potion2, potion3, potion4, potion5, potion6, potion7, potion8, potion9, potion10, scroll1, scroll2, scroll3, scroll4, scroll5, scroll6, scroll7, scroll8, scroll9, scroll10, scroll11, scroll12, scroll13, jumpingPole, sharpeningTool, seeingItem, motionScanner, gorget, necklace1, necklace2, necklace3, necklace4, necklace5, necklace6, necklace7, necklace8, necklace9, imageItensifier, sightSharpening, ring1, ring2, ring3, ring4, ring5, ring6, ring7, ring8, armorLeather, armorMail, gloveFencing, gloveGauntlet, gloveJousting, buckler, shield, dagger, daggerDropBestWeapon, hammer, hammerParalyze, hammerSpark, sword, swordImpress, swordNullify, halberd, halberdPushActor, wand1, wand2, gem1, gem2, gem3, gem4, gem5, currency :: ItemKind
+sandstoneRock,    dart, spike, slingStone, slingBullet, paralizingProj, harpoon, net, light1, light2, light3, blanket, flask1, flask2, flask3, flask4, flask5, flask6, flask7, flask8, flask9, flask10, flask11, flask12, flask13, flask14, flask15, flask16, flask17, flask18, flask19, flask20, potion1, potion2, potion3, potion4, potion5, potion6, potion7, potion8, potion9, potion10, scroll1, scroll2, scroll3, scroll4, scroll5, scroll6, scroll7, scroll8, scroll9, scroll10, scroll11, scroll12, scroll13, jumpingPole, sharpeningTool, seeingItem, motionScanner, gorget, necklace1, necklace2, necklace3, necklace4, necklace5, necklace6, necklace7, necklace8, necklace9, imageItensifier, sightSharpening, ring1, ring2, ring3, ring4, ring5, ring6, ring7, ring8, armorLeather, armorMail, gloveFencing, gloveGauntlet, gloveJousting, buckler, shield, dagger, daggerDropBestWeapon, hammer, hammerParalyze, hammerSpark, sword, swordImpress, swordNullify, halberd, halberdPushActor, wand1, wand2, gem1, gem2, gem3, gem4, gem5, currency, explosive, firecracker :: ItemKind
 
 necklace, ring, potion, flask, scroll, wand, gem :: ItemKind  -- generic templates
 
@@ -276,7 +276,7 @@
                , AddArmorMelee 1, AddMaxCalm 2 ]
   , ieffects = []
   , ifeature = [Lobable, Identified, Equipable]  -- not Fragile; reusable douse
-  , idesc    = ""
+  , idesc    = "Warm, comforting, and concealing, woven from soft wool."
   , ikit     = []
   }
 
@@ -293,6 +293,77 @@
 -- all aspects.
 --
 -- No flask nor temporary organ of Calm depletion, since Calm reduced often.
+
+-- All the explosive items are currently disabled (empty rarity),
+-- so that we can brainstorm them without breaking game balance.
+explosive = ItemKind
+  { isymbol = symbolFlask
+      -- if it's, in fact, a flask of laboratory-produced highly explosive
+      -- liquid, symbolFlask makes sense; it also suggests it can be lobbed,
+      -- which is very handy for an explosive thrown weapon;
+      -- but symbolProjectile makes sense, too, if it's essentially a small
+      -- grenade, specifically designed to be thrown, or symbolTool
+      -- if it's a heavy mining explosive
+  , iname = "explosive"  -- we will need something more specific, even it it's
+                         -- only a generic template item
+  , ifreq = [("useful", 100)]
+  , iflavour = zipLiquid [BrRed]
+      -- this ensures the object will be bright red on the screen
+      -- and will be described as "red-speckled"; we are also free
+      -- to manually set both the colour and the description
+  , icount = 1
+  , irarity = []  -- [(1, 7), (10, 4)] means it's quite common (7) first level
+                  -- and less common (4) on the deepest level of the dungeon,
+                  -- linearly interpolating in-between
+  , iverbHit = "blast"
+  , iweight = 500  -- flavour and affects the speed when thrown;
+                   -- 250g and below is full speed
+  , idamage = toDmg 0
+  , iaspects = []
+  , ieffects = [Explode "blast 20", OnSmash (Explode "blast 20")]
+      -- this is tricky: @OnSmash@ causes the explosion when the item
+      -- hits a tile (including floor), so a well aimed item can successfully
+      -- ungulf many enemies (or a single hidden or fast-moving enemy)
+      -- in an explosion; however the lone @Explode "blast 20"@
+      -- activates only when an actor is hit (or applies the item)
+      -- and makes the actor the centre of the explosion and so keeps him
+      -- unharmed (from the initial explosion; fireckrackers cause chain
+      -- explosion so the secondary blasts can harm him);
+      -- this is why "oil lamp", which is the closest to a grenade
+      -- we currently have, doesn't have a lone Exlode effect,
+      -- but instead causes a couple of unplesant effects directly:
+      -- slight burning, before the fire is doused, and paralysis
+      -- from slipping on the oil and slippery grip of items,
+      -- until the oil can be wiped out; it also causes a tiny impact damage;
+      -- all this suggests that we should define "blastI" item
+      -- that not only blasts outward, but also inward, by first creating
+      -- particles that move 1 (or 2?) tle away outward and then starting
+      -- a standard "blast" explosion in each (firecracker does that,
+      -- but with many, not just 2 levels of explosions)
+  , ifeature = [Lobable, Fragile, Identified, toVelocity 50]
+      -- not Applicable, because won't be applied, only thrown;
+      -- Fragile, because we want it to explode at target tile, even if
+      -- it hits no actor nor wall when it gets there;
+      -- Identified, because the player recognizes it from the description
+      -- already (unless we design granades that all look alike, but some
+      -- explode with blast, others with fireckracker, etc. and the player
+      -- needs to experiment to learn to recognize them);
+      -- @toVelocity 50@ means will have 50% of the speed of an ideally shaped
+      -- item of the same weight
+  , idesc = "The practical application of science."
+  , ikit = []
+  }
+
+firecracker = explosive
+  { iname = "firecracker bag/stick?"
+     -- remove the revealing name if we go the "10 kinds of grenades" route,
+     -- but make sure it's not too similar to the current "20 kinds of flasks
+     -- and potions that also explode" fun
+  , iverbHit = "crack"  -- a pun, matches the verb from ItemKindBlast.hs
+  , ieffects = [Explode "firecracker 5", OnSmash (Explode "firecracker 5")]
+  , idesc = "String and paper, concealing a deadly surprise."
+  }
+
 flask = ItemKind
   { isymbol  = symbolFlask
   , iname    = "flask"
@@ -473,6 +544,7 @@
   , irarity  = [(6, 9), (10, 9)]
   , ieffects = [ Unique, ELabel "of Attraction", Impress, RefillCalm (-20)
                , OnSmash (Explode "pheromone") ]
+  -- , idesc    = ""
   }
 potion3 = potion
   { ieffects = [ RefillHP 5, DropItem 1 maxBound COrgan "poisoned"
@@ -532,6 +604,7 @@
                , Impress, RefillCalm (-60)
                , OnSmash (Explode "healing mist 2")
                , OnSmash (Explode "pheromone") ]
+  -- , idesc    = ""
   }
 
 -- * Non-exploding consumables, not specifically designed for throwing
@@ -782,6 +855,7 @@
                , Recharging (RefillCalm (-1)) ]  -- fake "hears something" :)
                ++ ieffects necklace
   , ifeature = Durable : ifeature necklace
+  -- , idesc    = ""
   }
 necklace8 = necklace
   { iaspects = [Timeout $ (1 `d` 3 + 3 - 1 `dl` 3) * 5]
@@ -896,6 +970,7 @@
   , ieffects = [ Unique, ELabel "of Rush"  -- no explosion, because Durable
                , EqpSlot EqpSlotAddSpeed ]
   , ifeature = Durable : ifeature ring
+  -- , idesc    = ""
   }
 ring7 = ring
   { ifreq    = [("useful", 10), ("ring of opportunity sniper", 1) ]
@@ -1098,6 +1173,7 @@
   , idamage  = toDmg $ 8 `d` 1
   , iaspects = iaspects hammer ++ [Timeout $ (1 `d` 2 + 3 - 1 `dl` 2) * 2]
   , ieffects = ieffects hammer ++ [Unique, Recharging $ Paralyze 10]
+  -- , idesc    = ""
   }
 hammerSpark = hammer
   { iname    = "Grand Smithhammer"
@@ -1106,6 +1182,7 @@
   , idamage  = toDmg $ 12 `d` 1
   , iaspects = iaspects hammer ++ [AddShine 3, Timeout $ (1 `d` 4 + 4 - 1 `dl` 4) * 2]
   , ieffects = ieffects hammer ++ [Unique, Recharging $ Explode "spark"]
+  -- , idesc    = ""
   }
 sword = ItemKind
   { isymbol  = symbolEdged
diff --git a/GameDefinition/Content/ItemKindActor.hs b/GameDefinition/Content/ItemKindActor.hs
--- a/GameDefinition/Content/ItemKindActor.hs
+++ b/GameDefinition/Content/ItemKindActor.hs
@@ -22,7 +22,6 @@
    geyserBoiling, geyserArsenic, geyserSulfur :: ItemKind
 
 -- * Hunams
-
 warrior = ItemKind
   { isymbol  = '@'
   , iname    = "warrior"  -- modified if initial actors in hero faction
@@ -39,18 +38,26 @@
                , AddAbility AbAlter 2 ]
   , ieffects = []
   , ifeature = [Durable, Identified]
-  , idesc    = ""
+  , idesc    = "A hardened veteran of combat."
   , ikit     = [ ("fist", COrgan), ("foot", COrgan), ("eye 6", COrgan)
                , ("sapient brain", COrgan) ]
   }
 warrior2 = warrior
-  { iname    = "adventurer" }
+  { iname    = "adventurer"
+  -- , idesc    = ""
+  }
 warrior3 = warrior
-  { iname    = "blacksmith" }
+  { iname    = "blacksmith"
+  -- , idesc    = ""
+   }
 warrior4 = warrior
-  { iname    = "forester" }
+  { iname    = "forester"
+  -- , idesc    = ""
+  }
 warrior5 = warrior
-  { iname    = "scientist" }
+  { iname    = "scientist"
+  -- , idesc    = ""
+  }
 
 scout = warrior
   { iname    = "scout"
@@ -59,11 +66,13 @@
                ++ [ ("add sight", CEqp)
                   , ("armor ranged", CEqp)
                   , ("add nocto 1", CInv) ]
+  -- , idesc    = ""
   }
 ranger = warrior
   { iname    = "ranger"
   , ifreq    = [("ranger hero", 100), ("mobile", 1)]
   , ikit     = ikit warrior ++ [("weak arrow", CInv), ("armor ranged", CEqp)]
+  -- , idesc    = ""
   }
 escapist = warrior
   { iname    = "escapist"
@@ -75,6 +84,7 @@
                   , ("flask", CInv)
                   , ("light source", CInv)
                   , ("blanket", CInv) ]
+  -- , idesc    = ""
   }
 ambusher = warrior
   { iname    = "ambusher"
@@ -83,25 +93,37 @@
                ++ [ ("ring of opportunity sniper", CEqp)
                   , ("light source", CEqp), ("wooden torch", CInv)
                   , ("weak arrow", CInv), ("any arrow", CSha), ("flask", CSha) ]
+  -- , idesc    = ""
   }
 soldier = warrior
   { iname    = "soldier"
   , ifreq    = [("soldier hero", 100), ("mobile", 1)]
   , ikit     = ikit warrior ++ [("starting weapon", CEqp)]
+  -- , idesc    = ""
   }
 
 civilian = warrior
   { iname    = "clerk"
   , ifreq    = [("civilian", 100), ("mobile", 1)]
-  , iflavour = zipPlain [BrBlack] }
+  , iflavour = zipPlain [BrBlack]
+  -- , idesc    = ""
+  }
 civilian2 = civilian
-  { iname    = "hairdresser" }
+  { iname    = "hairdresser"
+  -- , idesc    = ""
+  }
 civilian3 = civilian
-  { iname    = "lawyer" }
+  { iname    = "lawyer"
+  -- , idesc    = ""
+  }
 civilian4 = civilian
-  { iname    = "peddler" }
+  { iname    = "peddler"
+  -- , idesc    = ""
+  }
 civilian5 = civilian
-  { iname    = "tax collector" }
+  { iname    = "tax collector"
+  -- , idesc    = ""
+  }
 
 -- * Monsters
 
@@ -238,7 +260,7 @@
   , iaspects = [ AddMaxHP 12, AddMaxCalm 70, AddSpeed 24, AddNocto 2 ]
   , ieffects = []
   , ifeature = [Durable, Identified]
-  , idesc    = ""
+  , idesc    = "An opportunistic predator, feeding on carrion and the weak."
   , ikit     = [ ("small jaw", COrgan), ("eye 6", COrgan), ("nostril", COrgan)
                , ("animal brain", COrgan) ]
   }
@@ -261,7 +283,7 @@
       -- on either side, are just too frustrating.
   , ieffects = []
   , ifeature = [Durable, Identified]
-  , idesc    = ""
+  , idesc    = "It soars high above, searching for vulnerable prey."
   , ikit     = [ ("screeching beak", COrgan)  -- in reality it grunts and hisses
                , ("small claw", COrgan), ("eye 7", COrgan)
                , ("animal brain", COrgan) ]
@@ -280,7 +302,7 @@
                , AddAbility AbAlter (-2) ]  -- can't use stairs nor doors
   , ieffects = []
   , ifeature = [Durable, Identified]
-  , idesc    = ""
+  , idesc    = "Its only defence is the terrible stench."
   , ikit     = [ ("scent gland", COrgan)
                , ("small claw", COrgan), ("snout", COrgan)
                , ("nostril", COrgan), ("eye 3", COrgan)
@@ -300,7 +322,7 @@
                , AddAbility AbAlter (-2) ]  -- can't use stairs nor doors
   , ieffects = []
   , ifeature = [Durable, Identified]
-  , idesc    = ""
+  , idesc    = "When threatened, it rolls into a ball."
   , ikit     = [ ("hooked claw", COrgan), ("snout", COrgan)
                , ("armored skin", COrgan), ("armored skin", COrgan)
                , ("nostril", COrgan), ("eye 3", COrgan)
@@ -320,7 +342,7 @@
                , AddAbility AbAlter (-2) ]  -- can't use stairs nor doors
   , ieffects = []
   , ifeature = [Durable, Identified]
-  , idesc    = ""
+  , idesc    = "Numbing venom ensures that even the fastest prey has no escape."
   , ikit     = [ ("venom tooth", COrgan), ("small claw", COrgan)
                , ("eye 3", COrgan), ("nostril", COrgan)
                , ("animal brain", COrgan) ]
@@ -339,7 +361,7 @@
                , AddAbility AbAlter (-2) ]  -- can't use stairs nor doors
   , ieffects = []
   , ifeature = [Durable, Identified]
-  , idesc    = ""
+  , idesc    = "Beware its rattle - it serves as a warning of an agonising death."
   , ikit     = [ ("venom fang", COrgan)
                , ("eye 4", COrgan), ("nostril", COrgan)
                , ("animal brain", COrgan) ]
@@ -357,7 +379,7 @@
   , iaspects = [ AddMaxHP 41, AddMaxCalm 60, AddSpeed 18, AddNocto 2 ]
   , ieffects = []
   , ifeature = [Durable, Identified]
-  , idesc    = ""
+  , idesc    = "Larger and more aggressive than any other lizard."
   , ikit     = [ ("large tail", COrgan), ("jaw", COrgan)
                , ("hooked claw", COrgan), ("speed gland 4", COrgan)
                , ("armored skin", COrgan), ("eye 3", COrgan)
@@ -377,7 +399,7 @@
   , iaspects = [ AddMaxHP 20, AddMaxCalm 70, AddSpeed 32, AddNocto 2 ]
   , ieffects = []
   , ifeature = [Durable, Identified]
-  , idesc    = ""
+  , idesc    = "Skulking in the shadows, waiting for easy prey."
   , ikit     = [ ("jaw", COrgan), ("eye 6", COrgan), ("nostril", COrgan)
                , ("animal brain", COrgan) ]
   }
@@ -394,7 +416,7 @@
   , iaspects = [ AddMaxHP 41, AddMaxCalm 70, AddSpeed 18, AddNocto 2 ]
   , ieffects = []
   , ifeature = [Durable, Identified]
-  , idesc    = ""
+  , idesc    = "An armored predator from the dawn of time."
   , ikit     = [ ("large jaw", COrgan), ("large tail", COrgan)
                , ("small claw", COrgan)
                , ("armored skin", COrgan), ("eye 6", COrgan)
@@ -438,7 +460,7 @@
                , AddAbility AbAlter (-2) ]  -- can't use stairs nor doors
   , ieffects = []
   , ifeature = [Durable, Identified]
-  , idesc    = ""
+  , idesc    = "Every bee would die for the queen."
   , ikit     = [ ("bee sting", COrgan), ("vision 6", COrgan)
                , ("insect mortality", COrgan), ("animal brain", COrgan) ]
   }
@@ -457,7 +479,7 @@
                , AddArmorMelee 80, AddArmorRanged 40 ]
   , ieffects = []
   , ifeature = [Durable, Identified]
-  , idesc    = ""
+  , idesc    = "A vicious cloud of stings and hate."
   , ikit     = [ ("sting", COrgan), ("vision 8", COrgan)
                , ("insect mortality", COrgan), ("animal brain", COrgan) ]
   }
@@ -475,7 +497,7 @@
                , AddAbility AbWait 1, AddAbility AbMelee 1 ]
   , ieffects = []
   , ifeature = [Durable, Identified]
-  , idesc    = ""
+  , idesc    = "Each branch bears long, curved thorns."
   , ikit     = [("thorn", COrgan), ("armored skin", COrgan)]
   }
 geyserBoiling = ItemKind
@@ -493,7 +515,7 @@
                , AddArmorMelee 40, AddArmorRanged 20 ]  -- hard material
   , ieffects = []
   , ifeature = [Durable, Identified]
-  , idesc    = ""
+  , idesc    = "A jet of acidic water, hot enough to melt flesh."
   , ikit     = [("boiling vent", COrgan), ("boiling fissure", COrgan)]
   }
 geyserArsenic = ItemKind
@@ -511,7 +533,7 @@
                , AddAbility AbWait 1, AddAbility AbMelee 1 ]
   , ieffects = []
   , ifeature = [Durable, Identified]
-  , idesc    = ""
+  , idesc    = "The sharp scent betrays the poison within the spray."
   , ikit     = [("arsenic vent", COrgan), ("arsenic fissure", COrgan)]
   }
 geyserSulfur = ItemKind
@@ -529,6 +551,6 @@
                , AddAbility AbWait 1, AddAbility AbMelee 1 ]
   , ieffects = []
   , ifeature = [Durable, Identified]
-  , idesc    = ""
+  , idesc    = "The pool boils and bubbles, stinking of rotten eggs. Despite the smell, these waters purify and strengthen."
   , ikit     = [("sulfur vent", COrgan), ("sulfur fissure", COrgan)]
   }
diff --git a/GameDefinition/Content/ItemKindBlast.hs b/GameDefinition/Content/ItemKindBlast.hs
--- a/GameDefinition/Content/ItemKindBlast.hs
+++ b/GameDefinition/Content/ItemKindBlast.hs
@@ -87,7 +87,7 @@
                     $ toGroupName $ "firecracker" <+> tshow (n - 1)
                   | n > 2 ]
   , ifeature = [toVelocity 5, Fragile, Identified]
-  , idesc    = ""
+  , idesc    = "Scraps of burnt paper, covering little pockets of black powder, buffeted by colorful explosions."
   , ikit     = []
   }
 firecracker7 = firecracker 7
@@ -114,7 +114,7 @@
   -- Linger 10, because sometimes it takes 2 turns due to starting just
   -- before actor turn's end (e.g., via a necklace).
   , ifeature = [toLinger 10, Fragile, Identified]  -- 2 steps, 1 turn
-  , idesc    = ""
+  , idesc    = "A pleasant scent."
   , ikit     = []
   }
 pheromone = ItemKind
@@ -130,7 +130,7 @@
   , iaspects = []
   , ieffects = [Impress, RefillCalm (-10)]
   , ifeature = [toVelocity 10, Fragile, Identified]  -- 2 steps, 2 turns
-  , idesc    = ""
+  , idesc    = "A sharp, strong scent."
   , ikit     = []
   }
 mistCalming = ItemKind  -- unused
@@ -146,7 +146,7 @@
   , iaspects = []
   , ieffects = [RefillCalm 2]
   , ifeature = [toVelocity 5, Fragile, Identified]  -- 1 step, 1 turn
-  , idesc    = ""
+  , idesc    = "A soothing, gentle cloud."
   , ikit     = []
   }
 odorDistressing = ItemKind
@@ -162,7 +162,7 @@
   , iaspects = []
   , ieffects = [RefillCalm (-20)]
   , ifeature = [toLinger 10, Fragile, Identified]  -- 2 steps, 1 turn
-  , idesc    = ""
+  , idesc    = "It turns the stomach."
   , ikit     = []
   }
 mistHealing = ItemKind
@@ -178,7 +178,7 @@
   , iaspects = [AddShine 1]
   , ieffects = [RefillHP 2]
   , ifeature = [toVelocity 5, Fragile, Identified]  -- 1 step, 1 turn
-  , idesc    = ""
+  , idesc    = "It fills the air with light and life."
   , ikit     = []
   }
 mistHealing2 = ItemKind
@@ -194,7 +194,7 @@
   , iaspects = [AddShine 2]
   , ieffects = [RefillHP 4]
   , ifeature = [toVelocity 5, Fragile, Identified]  -- 1 step, 1 turn
-  , idesc    = ""
+  , idesc    = "At its touch, wounds close and bruises fade."
   , ikit     = []
   }
 mistWounding = ItemKind
@@ -210,7 +210,7 @@
   , iaspects = []
   , ieffects = [RefillHP (-2)]
   , ifeature = [toVelocity 5, Fragile, Identified]  -- 1 step, 1 turn
-  , idesc    = ""
+  , idesc    = "The air itself stings and itches."
   , ikit     = []
   }
 distortion = ItemKind
@@ -226,7 +226,7 @@
   , iaspects = []
   , ieffects = [Teleport $ 15 + 1 `d` 10]
   , ifeature = [toLinger 10, Fragile, Identified]  -- 2 steps, 1 turn
-  , idesc    = ""
+  , idesc    = "The air shifts oddly, as though light is being warped."
   , ikit     = []
   }
 glassPiece = ItemKind  -- when blowing up windows
@@ -242,7 +242,7 @@
   , iaspects = []
   , ieffects = [RefillHP (-1)]  -- high velocity, so can't do idamage
   , ifeature = [toLinger 20, Fragile, Identified]  -- 4 steps, 1 turn
-  , idesc    = ""
+  , idesc    = "Swift, sharp edges."
   , ikit     = []
   }
 smoke = ItemKind  -- when stuff burns out  -- unused
@@ -258,7 +258,7 @@
   , iaspects = []
   , ieffects = []
   , ifeature = [toVelocity 20, Fragile, Identified]  -- 4 steps, 2 turns
-  , idesc    = ""
+  , idesc    = "Twirling clouds of grey smoke."
   , ikit     = []
   }
 boilingWater = ItemKind
@@ -274,7 +274,7 @@
   , iaspects = []
   , ieffects = [Burn 1]
   , ifeature = [toVelocity 30, Fragile, Identified]  -- 6 steps, 2 turns
-  , idesc    = ""
+  , idesc    = "It bubbles and hisses."
   , ikit     = []
   }
 glue = ItemKind
@@ -290,7 +290,7 @@
   , iaspects = []
   , ieffects = [Paralyze 10]
   , ifeature = [toVelocity 20, Fragile, Identified]  -- 4 steps, 2 turns
-  , idesc    = ""
+  , idesc    = "Thick and clinging."
   , ikit     = []
   }
 singleSpark = ItemKind
@@ -306,7 +306,7 @@
   , iaspects = [AddShine 4]
   , ieffects = []
   , ifeature = [toLinger 5, Fragile, Identified]  -- 1 step, 1 turn
-  , idesc    = ""
+  , idesc    = "A glowing ember."
   , ikit     = []
   }
 spark = ItemKind
@@ -322,7 +322,7 @@
   , iaspects = [AddShine 4]
   , ieffects = [Burn 1]
   , ifeature = [toLinger 10, Fragile, Identified]  -- 2 steps, 1 turn
-  , idesc    = ""
+  , idesc    = "A flash of fire."
   , ikit     = []
   }
 
@@ -345,7 +345,7 @@
   , iaspects = []
   , ieffects = [toOrganActorTurn "strengthened" (3 + 1 `d` 3)]
   , ifeature = [toLinger 10, Fragile, Identified]
-  , idesc    = ""
+  , idesc    = "A thick rain of droplets."
   , ikit     = []
   }
 sparseShower = ItemKind
@@ -361,7 +361,7 @@
   , iaspects = []
   , ieffects = [toOrganGameTurn "weakened" (3 + 1 `d` 3)]
   , ifeature = [toLinger 10, Fragile, Identified]
-  , idesc    = ""
+  , idesc    = "Light droplets that cling to clothing."
   , ikit     = []
   }
 protectingBalmMelee = ItemKind
@@ -377,7 +377,7 @@
   , iaspects = []
   , ieffects = [toOrganActorTurn "protected from melee" (3 + 1 `d` 3)]
   , ifeature = [toLinger 10, Fragile, Identified]
-  , idesc    = ""
+  , idesc    = "A thick ointment that hardens the skin."
   , ikit     = []
   }
 protectingBalmRanged = ItemKind
@@ -393,7 +393,7 @@
   , iaspects = []
   , ieffects = [toOrganActorTurn "protected from ranged" (3 + 1 `d` 3)]
   , ifeature = [toLinger 10, Fragile, Identified]
-  , idesc    = ""
+  , idesc    = "Grease that protects from flying death."
   , ikit     = []
   }
 vulnerabilityBalm = ItemKind
@@ -409,7 +409,7 @@
   , iaspects = []
   , ieffects = [toOrganGameTurn "defenseless" (3 + 1 `d` 3)]
   , ifeature = [toLinger 10, Fragile, Identified]
-  , idesc    = ""
+  , idesc    = "Only the most learned make use of this."
   , ikit     = []
   }
 resolutionDust = ItemKind
@@ -426,7 +426,7 @@
   , ieffects = [toOrganActorTurn "resolute" (3 + 1 `d` 3)]
                  -- short enough duration that @calmEnough@ not a big problem
   , ifeature = [toLinger 10, Fragile, Identified]
-  , idesc    = ""
+  , idesc    = "A handful of honest earth, to strengthen the soul."
   , ikit     = []
   }
 hasteSpray = ItemKind
@@ -442,7 +442,7 @@
   , iaspects = []
   , ieffects = [toOrganActorTurn "hasted" (3 + 1 `d` 3)]
   , ifeature = [toLinger 10, Fragile, Identified]
-  , idesc    = ""
+  , idesc    = "A quick spurt."
   , ikit     = []
   }
 slownessMist = ItemKind
@@ -458,7 +458,7 @@
   , iaspects = []
   , ieffects = [toOrganGameTurn "slowed" (3 + 1 `d` 3)]
   , ifeature = [toVelocity 5, Fragile, Identified]  -- 1 step, 1 turn, mist
-  , idesc    = ""
+  , idesc    = "Clammy fog, making each movement an effort."
   , ikit     = []
   }
 eyeDrop = ItemKind
@@ -474,7 +474,7 @@
   , iaspects = []
   , ieffects = [toOrganActorTurn "far-sighted" (3 + 1 `d` 3)]
   , ifeature = [toLinger 10, Fragile, Identified]
-  , idesc    = ""
+  , idesc    = "Not to be taken orally."
   , ikit     = []
   }
 ironFiling = ItemKind
@@ -490,7 +490,7 @@
   , iaspects = []
   , ieffects = [toOrganActorTurn "blind" (10 + 1 `d` 10)]
   , ifeature = [toLinger 10, Fragile, Identified]
-  , idesc    = ""
+  , idesc    = "A shaving of bright metal."
   , ikit     = []
   }
 smellyDroplet = ItemKind
@@ -506,7 +506,7 @@
   , iaspects = []
   , ieffects = [toOrganActorTurn "keen-smelling" (3 + 1 `d` 3)]
   , ifeature = [toLinger 10, Fragile, Identified]
-  , idesc    = ""
+  , idesc    = "A viscous lump that stains the skin."
   , ikit     = []
   }
 eyeShine = ItemKind
@@ -522,7 +522,7 @@
   , iaspects = []
   , ieffects = [toOrganActorTurn "shiny-eyed" (3 + 1 `d` 3)]
   , ifeature = [toLinger 10, Fragile, Identified]
-  , idesc    = ""
+  , idesc    = "They almost glow in the dark."
   , ikit     = []
   }
 
@@ -541,7 +541,7 @@
   , iaspects = []
   , ieffects = [toOrganActorTurn "drunk" (3 + 1 `d` 3)]
   , ifeature = [toLinger 10, Fragile, Identified]
-  , idesc    = ""
+  , idesc    = "It burns in the best way."
   , ikit     = []
   }
 waste = ItemKind
@@ -557,7 +557,7 @@
   , iaspects = []
   , ieffects = [Burn 1]
   , ifeature = [toLinger 10, Fragile, Identified]
-  , idesc    = ""
+  , idesc    = "Sodden and foul-smelling."
   , ikit     = []
   }
 youthSprinkle = ItemKind
@@ -573,7 +573,7 @@
   , iaspects = []
   , ieffects = [toOrganNone "regenerating"]
   , ifeature = [toLinger 10, Fragile, Identified]
-  , idesc    = ""
+  , idesc    = "Bright and smelling of the Spring."
   , ikit     = []
   }
 poisonCloud = ItemKind
@@ -589,7 +589,7 @@
   , iaspects = []
   , ieffects = [toOrganNone "poisoned"]
   , ifeature = [toVelocity 10, Fragile, Identified]  -- 2 steps, 2 turns
-  , idesc    = ""
+  , idesc    = "Choking gas that stings the eyes."
   , ikit     = []
   }
 mistAntiSlow = ItemKind
@@ -605,7 +605,7 @@
   , iaspects = []
   , ieffects = [DropItem 1 1 COrgan "slowed"]
   , ifeature = [toVelocity 5, Fragile, Identified]  -- 1 step, 1 turn
-  , idesc    = ""
+  , idesc    = "A cleansing rain."
   , ikit     = []
   }
 mistAntidote = ItemKind
@@ -621,6 +621,6 @@
   , iaspects = []
   , ieffects = [DropItem 1 maxBound COrgan "poisoned"]
   , ifeature = [toVelocity 5, Fragile, Identified]  -- 1 step, 1 turn
-  , idesc    = ""
+  , idesc    = "Washes away death's dew."
   , ikit     = []
   }
diff --git a/GameDefinition/Content/ItemKindEmbed.hs b/GameDefinition/Content/ItemKindEmbed.hs
--- a/GameDefinition/Content/ItemKindEmbed.hs
+++ b/GameDefinition/Content/ItemKindEmbed.hs
@@ -32,7 +32,7 @@
   , iaspects = []
   , ieffects = [Ascend True]
   , ifeature = [Identified, Durable]
-  , idesc    = ""
+  , idesc    = "Stairs that rise towards escape."
   , ikit     = []
   }
 stairsDown = stairsUp
@@ -40,12 +40,14 @@
   , iname    = "staircase down"
   , ifreq    = [("staircase down", 1)]
   , ieffects = [Ascend False]
+  , idesc    = ""
   }
 escape = stairsUp
   { iname    = "escape"
   , ifreq    = [("escape", 1)]
   , iflavour = zipPlain [BrYellow]
   , ieffects = [Escape]
+  , idesc    = ""
   }
 terrainCache = stairsUp
   { isymbol  = 'O'
@@ -53,6 +55,7 @@
   , ifreq    = [("terrain cache", 1)]
   , iflavour = zipPlain [BrYellow]
   , ieffects = [CreateItem CGround "useful" TimerNone]
+  , idesc    = "Glittering gold, just waiting to be taken."
   }
 terrainCacheTrap = ItemKind
   { isymbol  = '^'
@@ -70,7 +73,7 @@
                       , ELabel "", ELabel "", ELabel ""
                       , ELabel "", ELabel "" ]]
   , ifeature = [Identified]  -- not Durable, springs at most once
-  , idesc    = ""
+  , idesc    = "It's a trap!"
   , ikit     = []
   }
 signboardExit = ItemKind
@@ -86,7 +89,7 @@
   , iaspects = []
   , ieffects = [DetectExit 100]
   , ifeature = [Identified, Durable]
-  , idesc    = ""
+  , idesc    = "A battered sign, carved by unknown hands."
   , ikit     = []
   }
 signboardMap = signboardExit
@@ -107,7 +110,7 @@
   , iaspects = []
   , ieffects = [Burn 1, Explode "single spark"]
   , ifeature = [Identified, Durable]
-  , idesc    = ""
+  , idesc    = "A few small logs, burning brightly."
   , ikit     = []
   }
 fireBig = fireSmall
@@ -117,7 +120,7 @@
   , ieffects = [ Burn 2, Explode "spark"
                , CreateItem CInv "wooden torch" TimerNone ]
   , ifeature = [Identified, Durable]
-  , idesc    = ""
+  , idesc    = "Glowing with light and warmth."
   , ikit     = []
   }
 frost = ItemKind
@@ -135,7 +138,7 @@
                , RefillCalm 20  -- cold reason
                , PushActor (ThrowMod 200 50) ]  -- slippery ice
   , ifeature = [Identified, Durable]
-  , idesc    = ""
+  , idesc    = "Intricate patterns of shining ice."
   , ikit     = []
   }
 rubble = ItemKind
@@ -155,7 +158,7 @@
                       , ELabel "", ELabel "", ELabel ""
                       , ELabel "", ELabel "", ELabel "" ]]
   , ifeature = [Identified, Durable]
-  , idesc    = ""
+  , idesc    = "Broken chunks of rock and glass."
   , ikit     = []
   }
 staircaseTrapUp = ItemKind
@@ -171,7 +174,7 @@
   , iaspects = []
   , ieffects = [Temporary "be caught in an updraft", Teleport 5]
   , ifeature = [Identified]  -- not Durable, springs at most once
-  , idesc    = ""
+  , idesc    = "A hidden spring, to help the unwary soar."
   , ikit     = []
   }
 -- Needs to be separate from staircaseTrapUp, to make sure the item is
@@ -181,6 +184,7 @@
   { ifreq    = [("staircase trap down", 1)]
   , ieffects = [ Temporary "tumble down the stairwell"
                , toOrganActorTurn "drunk" (20 + 1 `d` 5) ]
+  , idesc    = "A treacherous slab, to teach those who are too proud."
   }
 doorwayTrap = ItemKind
   { isymbol  = '^'
@@ -197,7 +201,7 @@
                       , toOrganActorTurn "slowed" (20 + 1 `d` 5)
                       , toOrganActorTurn "weakened" (20 + 1 `d` 5) ]]
   , ifeature = [Identified]  -- not Durable, springs at most once
-  , idesc    = ""
+  , idesc    = "Just turn the handle..."
   , ikit     = []
   }
 obscenePictograms = ItemKind
@@ -217,7 +221,7 @@
                    [ toOrganActorTurn "strengthened" (3 + 1 `d` 3)
                    , CreateItem CInv "sandstone rock" TimerNone ] ]
   , ifeature = [Identified, Durable]
-  , idesc    = ""
+  , idesc    = "They aren't even anatomically possible."
   , ikit     = []
   }
 subtleFresco = ItemKind
@@ -236,7 +240,7 @@
                , Recharging $ toOrganActorTurn "far-sighted" (3 + 1 `d` 3)
                , Recharging $ toOrganActorTurn "keen-smelling" (3 + 1 `d` 3) ]
   , ifeature = [Identified, Durable]
-  , idesc    = ""
+  , idesc    = "Expensive yet tasteful."
   , ikit     = []
   }
 scratchOnWall = ItemKind
@@ -252,7 +256,7 @@
   , iaspects = []
   , ieffects = [Temporary "start making sense of the scratches", DetectHidden 3]
   , ifeature = [Identified, Durable]
-  , idesc    = ""
+  , idesc    = "A seemingly random series of scratches, carved deep into the wall."
   , ikit     = []
   }
 pulpit = ItemKind
@@ -270,6 +274,6 @@
                , toOrganGameTurn "defenseless" (20 + 1 `d` 5)
                , Explode "PhD defense question" ]
   , ifeature = [Identified]  -- not Durable, springs at most once
-  , idesc    = ""
+  , idesc    = "A dark wood stand, where strange priests once preached."
   , ikit     = []
   }
diff --git a/GameDefinition/Content/ItemKindOrgan.hs b/GameDefinition/Content/ItemKindOrgan.hs
--- a/GameDefinition/Content/ItemKindOrgan.hs
+++ b/GameDefinition/Content/ItemKindOrgan.hs
@@ -41,7 +41,7 @@
   , iaspects = []
   , ieffects = []
   , ifeature = [Durable, Identified, Meleeable]
-  , idesc    = ""
+  , idesc    = "Simple but effective."
   , ikit     = []
   }
 foot = fist
@@ -49,7 +49,7 @@
   , ifreq    = [("foot", 50)]
   , iverbHit = "kick"
   , idamage  = toDmg $ 4 `d` 1
-  , idesc    = ""
+  , idesc    = "A weapon you can still use if disarmed."
   }
 
 -- * Universal weapon organs
@@ -62,14 +62,14 @@
   , idamage  = toDmg $ 2 `d` 1
   , iaspects = [Timeout $ 4 + 1 `d` 4]
   , ieffects = [Recharging (toOrganGameTurn "slowed" 2)]
-  , idesc    = ""
+  , idesc    = "A curved talon."
   }
 smallClaw = fist
   { iname    = "small claw"
   , ifreq    = [("small claw", 50)]
   , iverbHit = "slash"
   , idamage  = toDmg $ 2 `d` 1
-  , idesc    = ""
+  , idesc    = "A pearly spike."
   }
 snout = fist
   { iname    = "snout"
@@ -77,7 +77,7 @@
   , icount   = 1
   , iverbHit = "bite"
   , idamage  = toDmg $ 2 `d` 1
-  , idesc    = ""
+  , idesc    = "Sensitive and wide-nostrilled."
   }
 smallJaw = fist
   { iname    = "small jaw"
@@ -85,7 +85,7 @@
   , icount   = 1
   , iverbHit = "rip"
   , idamage  = toDmg $ 3 `d` 1
-  , idesc    = ""
+  , idesc    = "Filled with small, even teeth."
   }
 jaw = fist
   { iname    = "jaw"
@@ -93,7 +93,7 @@
   , icount   = 1
   , iverbHit = "rip"
   , idamage  = toDmg $ 5 `d` 1
-  , idesc    = ""
+  , idesc    = "Delivers a powerful bite."
   }
 largeJaw = fist
   { iname    = "large jaw"
@@ -101,7 +101,7 @@
   , icount   = 1
   , iverbHit = "crush"
   , idamage  = toDmg $ 10 `d` 1
-  , idesc    = ""
+  , idesc    = "Enough to swallow anything in a single gulp."
   }
 horn = fist
   { iname    = "horn"
@@ -110,7 +110,7 @@
   , iverbHit = "impale"
   , idamage  = toDmg $ 6 `d` 1
   , iaspects = [AddHurtMelee 20]
-  , idesc    = ""
+  , idesc    = "Sharp and solid, for defence or attack."
   }
 
 -- * Special weapon organs
@@ -121,7 +121,7 @@
   , icount   = 4
   , iverbHit = "slap"
   , idamage  = toDmg $ 4 `d` 1
-  , idesc    = ""
+  , idesc    = "Damp and dextrous."
   }
 thorn = fist
   { iname    = "thorn"
@@ -130,7 +130,7 @@
   , iverbHit = "impale"
   , idamage  = toDmg $ 1 `d` 3
   , ifeature = [Identified, Meleeable]  -- not Durable
-  , idesc    = ""
+  , idesc    = "Sharp yet brittle."
   }
 boilingFissure = fist
   { iname    = "fissure"
@@ -141,7 +141,7 @@
   , iaspects = [AddHurtMelee 20]  -- decreasing as count decreases
   , ieffects = [InsertMove $ 1 `d` 3]
   , ifeature = [Identified, Meleeable]  -- not Durable
-  , idesc    = ""
+  , idesc    = "A deep crack to the underworld."
   }
 arsenicFissure = boilingFissure
   { iname    = "fissure"
@@ -149,6 +149,7 @@
   , icount   = 3 + 1 `d` 3
   , idamage  = toDmg $ 2 `d` 1
   , ieffects = [toOrganGameTurn "weakened" (2 + 1 `d` 2)]
+  , idesc    = ""
   }
 sulfurFissure = boilingFissure
   { iname    = "fissure"
@@ -156,6 +157,7 @@
   , icount   = 2 + 1 `d` 2
   , idamage  = toDmg 0
   , ieffects = [RefillHP 5]
+  , idesc    = ""
   }
 beeSting = fist
   { iname    = "bee sting"
@@ -186,7 +188,7 @@
   , idamage  = toDmg $ 2 `d` 1
   , iaspects = [Timeout $ 5 + 1 `d` 3]
   , ieffects = [Recharging (toOrganGameTurn "slowed" (3 + 1 `d` 3))]
-  , idesc    = ""
+  , idesc    = "A chilling numbness spreads from its bite."
   }
 venomFang = fist
   { iname    = "venom fang"
@@ -196,7 +198,7 @@
   , idamage  = toDmg $ 2 `d` 1
   , iaspects = [Timeout $ 7 + 1 `d` 5]
   , ieffects = [Recharging (toOrganNone "poisoned")]
-  , idesc    = ""
+  , idesc    = "Dripping with deadly venom."
   }
 screechingBeak = fist
   { iname    = "screeching beak"
@@ -206,7 +208,7 @@
   , idamage  = toDmg $ 2 `d` 1
   , iaspects = [Timeout $ 5 + 1 `d` 5]
   , ieffects = [Recharging $ Summon "scavenger" $ 1 + 1 `dl` 2]
-  , idesc    = ""
+  , idesc    = "Both a weapon and a beacon, calling more scavengers to the meal."
   }
 largeTail = fist
   { iname    = "large tail"
@@ -216,7 +218,7 @@
   , idamage  = toDmg $ 6 `d` 1
   , iaspects = [Timeout $ 1 + 1 `d` 3, AddHurtMelee 20]
   , ieffects = [Recharging (PushActor (ThrowMod 400 25))]
-  , idesc    = ""
+  , idesc    = "Slow but heavy."
   }
 
 -- Non-weapons
@@ -236,7 +238,7 @@
   , iaspects = [AddArmorMelee 30, AddArmorRanged 15]
   , ieffects = []
   , ifeature = [Durable, Identified]
-  , idesc    = ""
+  , idesc    = "Homemade armour is just as good."
   , ikit     = []
   }
 
@@ -249,7 +251,7 @@
   , icount   = 2
   , iverbHit = "glare at"
   , iaspects = [AddSight (intToDice n)]
-  , idesc    = ""
+  , idesc    = "A piercing stare."
   }
 eye2 = eye 2
 eye3 = eye 3
diff --git a/GameDefinition/Content/ItemKindTemporary.hs b/GameDefinition/Content/ItemKindTemporary.hs
--- a/GameDefinition/Content/ItemKindTemporary.hs
+++ b/GameDefinition/Content/ItemKindTemporary.hs
@@ -42,7 +42,7 @@
                , Recharging $ tmpNoLonger name
                , OnSmash $ tmpNoLonger name ]
   , ifeature = [Identified, Fragile, Durable]  -- hack: destroy on drop
-  , idesc    = ""
+  , idesc    = ""  -- no description needed; stats are enough
   , ikit     = []
   }
 
diff --git a/GameDefinition/Main.hs b/GameDefinition/Main.hs
--- a/GameDefinition/Main.hs
+++ b/GameDefinition/Main.hs
@@ -13,8 +13,11 @@
 import qualified GHC.IO.Handle as GHC.IO.Handle
 import qualified Options.Applicative as OA
 import           System.Exit
+import           System.FilePath
 import qualified System.IO as SIO
 
+import Game.LambdaHack.Common.File (tryCreateDir)
+import Game.LambdaHack.Common.Misc
 import Game.LambdaHack.Server (serverOptionsPI)
 import TieKnot
 
@@ -25,8 +28,10 @@
   -- For the case when the game is started not on a console.
   isTerminal <- SIO.hIsTerminalDevice SIO.stdout
   unless isTerminal $ do
-    fstdout <- SIO.openFile "stdout.txt" SIO.WriteMode
-    fstderr <- SIO.openFile "stderr.txt" SIO.WriteMode
+    dataDir <- appDataDir
+    tryCreateDir dataDir
+    fstdout <- SIO.openFile (dataDir </> "stdout.txt") SIO.WriteMode
+    fstderr <- SIO.openFile (dataDir </> "stderr.txt") SIO.WriteMode
     GHC.IO.Handle.hDuplicateTo fstdout SIO.stdout
     GHC.IO.Handle.hDuplicateTo fstderr SIO.stderr
   serverOptions <- OA.execParser serverOptionsPI
diff --git a/GameDefinition/MainMenu.ascii b/GameDefinition/MainMenu.ascii
--- a/GameDefinition/MainMenu.ascii
+++ b/GameDefinition/MainMenu.ascii
@@ -1,9 +1,9 @@
 fffffjjjjtti,:Lft:                tDEGLLGEKEK;   .    .   . .  .iEDEGL.;iiij ...
 fLLLLfffjjjti;.fL .              GDDLfGDEDEf .    .  .      .    ;LDWEGi,;iit ..
 LD#WWELfffjjtt;.if.:           .DEGLLGEDLti,   . .    . . .        ,tLKDG ,;itt.
-fK#WWW#DLffjjtt;:;j...        tDEGfLDEDGtK.   .LambdaHack           itLEG::;itt.
-;K#WWWWW Lfffjjti:.t...      tDEGfLDEDL,   ..  .  .                  tDGj.;itt.
- EWWWWKW  GLffjjti: t,      tDEGLGEEDj;  .                            tLDLi,ittj
+fK#WWW#DLffjjtt;:;j...        tDEGfLDEDGtK.  . LambdaHack           itLEG::;itt.
+;K#WWWWW Lfffjjti:.t...      tDEGfLDEDL, .                           tDGj.;itt.
+ EWWWWKW  GLffjjti: t,      tDEGLGEEDj;   <lambdahack.github.io>      tLDLi,ittj
  EKWWWEWG  GLffjjti: tt   .tDEGGDEDGi,                                ;fEG :iitj
  LEWWWKKK   GLfffjti: tt  tDDLGDEDGf.  {{{{{{{{{{{{{{{{{{{{{{{{{{{{{{ ;jGG :iitj
   DWWWKDWL   DLffjjti: jt.DDLGDEDLD    {{{{{{{{{{{{{{{{{{{{{{{{{{{{{{ ,jDG .ittj
diff --git a/LICENSE b/LICENSE
--- a/LICENSE
+++ b/LICENSE
@@ -14,7 +14,7 @@
 The whole LambdaHack is licensed under the BSD3 licence, as follows.
 
 Copyright (c) 2008--2017 Andres Loeh
-Copyright (c) 2010--2017 Mikolaj Konarski
+Copyright (c) 2010--2017 Mikolaj Konarski and others (see git history)
 
 All rights reserved.
 
diff --git a/LambdaHack.cabal b/LambdaHack.cabal
--- a/LambdaHack.cabal
+++ b/LambdaHack.cabal
@@ -5,7 +5,7 @@
 -- PVP summary:+-+------- breaking API changes
 --             | | +----- minor or non-breaking API additions
 --             | | | +--- code changes with no API change
-version:       0.7.0.0
+version:       0.7.1.0
 synopsis:      A game engine library for roguelike dungeon crawlers
 description:   LambdaHack is a Haskell game engine library for roguelike games
                of arbitrary theme, size and complexity,
diff --git a/Makefile b/Makefile
--- a/Makefile
+++ b/Makefile
@@ -1,5 +1,5 @@
 play:
-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --dumpInitRngs
+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --logPriority 4 --dumpInitRngs
 
 configure-debug:
 	cabal configure --enable-profiling --profiling-detail=all-functions -fwith_expensive_assertions --disable-optimization
@@ -17,83 +17,81 @@
 	ccjs dist/build/LambdaHack/LambdaHack.jsexe/all.js --compilation_level=ADVANCED_OPTIMIZATIONS --isolation_mode=IIFE --assume_function_wrapper --jscomp_off="*" --externs=node > ../lambdahack.github.io/lambdahack.all.js
 
 frontendRaid:
-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --boostRandomItem --savePrefix test --newGame 5 --dumpInitRngs --automateAll --gameMode raid
+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --logPriority 4 --boostRandomItem --savePrefix test --newGame 5 --dumpInitRngs --automateAll --gameMode raid
 
 frontendBrawl:
-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --boostRandomItem --savePrefix test --newGame 5 --dumpInitRngs --automateAll --gameMode brawl
+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --logPriority 4 --boostRandomItem --savePrefix test --newGame 5 --dumpInitRngs --automateAll --gameMode brawl
 
 frontendShootout:
-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --boostRandomItem --savePrefix test --newGame 5 --dumpInitRngs --automateAll --gameMode shootout
+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --logPriority 4 --boostRandomItem --savePrefix test --newGame 5 --dumpInitRngs --automateAll --gameMode shootout
 
 frontendEscape:
-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --boostRandomItem --savePrefix test --newGame 3 --dumpInitRngs --automateAll --gameMode escape
+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --logPriority 4 --boostRandomItem --savePrefix test --newGame 3 --dumpInitRngs --automateAll --gameMode escape
 
 frontendZoo:
-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --boostRandomItem --savePrefix test --newGame 2 --dumpInitRngs --automateAll --gameMode zoo
+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --logPriority 4 --boostRandomItem --savePrefix test --newGame 2 --dumpInitRngs --automateAll --gameMode zoo
 
 frontendAmbush:
-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --boostRandomItem --savePrefix test --newGame 5 --dumpInitRngs --automateAll --gameMode ambush
+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --logPriority 4 --boostRandomItem --savePrefix test --newGame 5 --dumpInitRngs --automateAll --gameMode ambush
 
 frontendCrawl:
-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --boostRandomItem --savePrefix test --newGame 1 --dumpInitRngs --automateAll --gameMode crawl
+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --logPriority 4 --boostRandomItem --savePrefix test --newGame 1 --dumpInitRngs --automateAll --gameMode crawl
 
 frontendSafari:
-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --boostRandomItem --savePrefix test --newGame 2 --dumpInitRngs --automateAll --gameMode safari
+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --logPriority 4 --boostRandomItem --savePrefix test --newGame 2 --dumpInitRngs --automateAll --gameMode safari
 
 frontendSafariSurvival:
-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --boostRandomItem --savePrefix test --newGame 5 --dumpInitRngs --automateAll --gameMode "safari survival"
+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --logPriority 4 --boostRandomItem --savePrefix test --newGame 5 --dumpInitRngs --automateAll --gameMode "safari survival"
 
 frontendBattle:
-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --boostRandomItem --savePrefix test --newGame 5 --dumpInitRngs --automateAll --gameMode battle
+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --logPriority 4 --boostRandomItem --savePrefix test --newGame 5 --dumpInitRngs --automateAll --gameMode battle
 
 frontendBattleSurvival:
-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --boostRandomItem --savePrefix test --newGame 5 --dumpInitRngs --automateAll --gameMode "battle survival"
+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --logPriority 4 --boostRandomItem --savePrefix test --newGame 5 --dumpInitRngs --automateAll --gameMode "battle survival"
 
 frontendDefense:
-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --boostRandomItem --savePrefix test --newGame 9 --dumpInitRngs --automateAll --gameMode defense
+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --logPriority 4 --boostRandomItem --savePrefix test --newGame 9 --dumpInitRngs --automateAll --gameMode defense
 
 
 benchMemoryAnim:
-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --newGame 1 --maxFps 100000 --benchmark --stopAfterFrames 33000 --automateAll --keepAutomated --gameMode crawl --setDungeonRng 120 --setMainRng 47 --frontendNull --noAnim +RTS -s -A1M -RTS
+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --logPriority 4 --newGame 1 --maxFps 100000 --benchmark --stopAfterFrames 33000 --automateAll --keepAutomated --gameMode crawl --setDungeonRng 120 --setMainRng 47 --frontendNull --noAnim +RTS -s -A1M -RTS
 
 benchBattle:
-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --newGame 3 --noAnim --maxFps 100000 --frontendNull --benchmark --stopAfterFrames 1500 --automateAll --keepAutomated --gameMode battle --setDungeonRng 7 --setMainRng 7
+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --logPriority 4 --newGame 3 --noAnim --maxFps 100000 --frontendNull --benchmark --stopAfterFrames 1500 --automateAll --keepAutomated --gameMode battle --setDungeonRng 7 --setMainRng 7
 
 benchAnimBattle:
-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --newGame 3 --maxFps 100000 --frontendNull --benchmark --stopAfterFrames 7000 --automateAll --keepAutomated --gameMode battle --setDungeonRng 7 --setMainRng 7
+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --logPriority 4 --newGame 3 --maxFps 100000 --frontendNull --benchmark --stopAfterFrames 7000 --automateAll --keepAutomated --gameMode battle --setDungeonRng 7 --setMainRng 7
 
 benchFrontendBattle:
-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --newGame 3 --noAnim --maxFps 100000 --benchmark --stopAfterFrames 1500 --automateAll --keepAutomated --gameMode battle --setDungeonRng 7 --setMainRng 7
+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --logPriority 4 --newGame 3 --noAnim --maxFps 100000 --benchmark --stopAfterFrames 1500 --automateAll --keepAutomated --gameMode battle --setDungeonRng 7 --setMainRng 7
 
 benchCrawl:
-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --newGame 1 --noAnim --maxFps 100000 --frontendNull --benchmark --stopAfterFrames 7000 --automateAll --keepAutomated --gameMode crawl --setDungeonRng 0 --setMainRng 0
+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --logPriority 4 --newGame 1 --noAnim --maxFps 100000 --frontendNull --benchmark --stopAfterFrames 7000 --automateAll --keepAutomated --gameMode crawl --setDungeonRng 0 --setMainRng 0
 
 benchFrontendCrawl:
-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --newGame 1 --noAnim --maxFps 100000 --benchmark --stopAfterFrames 7000 --automateAll --keepAutomated --gameMode crawl --setDungeonRng 0 --setMainRng 0
+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --logPriority 4 --newGame 1 --noAnim --maxFps 100000 --benchmark --stopAfterFrames 7000 --automateAll --keepAutomated --gameMode crawl --setDungeonRng 0 --setMainRng 0
 
 benchNull: benchBattle benchAnimBattle benchCrawl
 
 bench: benchBattle benchAnimBattle benchFrontendBattle benchCrawl benchFrontendCrawl
 
 nativeBenchCrawl:
-	dist/build/LambdaHack/LambdaHack		   --dbgMsgSer --newGame 2 --noAnim --maxFps 100000 --frontendNull --benchmark --stopAfterFrames 2000 --automateAll --keepAutomated --gameMode crawl --setDungeonRng 0 --setMainRng 0
+	dist/build/LambdaHack/LambdaHack		   --dbgMsgSer --logPriority 4 --newGame 2 --noAnim --maxFps 100000 --frontendNull --benchmark --stopAfterFrames 2000 --automateAll --keepAutomated --gameMode crawl --setDungeonRng 0 --setMainRng 0
 
 nativeBenchBattle:
-	dist/build/LambdaHack/LambdaHack		   --dbgMsgSer --newGame 3 --noAnim --maxFps 100000 --frontendNull --benchmark --stopAfterFrames 1000 --automateAll --keepAutomated --gameMode battle --setDungeonRng 0 --setMainRng 0
+	dist/build/LambdaHack/LambdaHack		   --dbgMsgSer --logPriority 4 --newGame 3 --noAnim --maxFps 100000 --frontendNull --benchmark --stopAfterFrames 1000 --automateAll --keepAutomated --gameMode battle --setDungeonRng 0 --setMainRng 0
 
 nativeBench: nativeBenchBattle nativeBenchCrawl
 
 nodeBenchCrawl:
-	node dist/build/LambdaHack/LambdaHack.jsexe/all.js --dbgMsgSer --newGame 2 --noAnim --maxFps 100000 --frontendNull --benchmark --stopAfterFrames 2000 --automateAll --keepAutomated --gameMode crawl --setDungeonRng 0 --setMainRng 0
+	node dist/build/LambdaHack/LambdaHack.jsexe/all.js --dbgMsgSer --logPriority 4 --newGame 2 --noAnim --maxFps 100000 --frontendNull --benchmark --stopAfterFrames 2000 --automateAll --keepAutomated --gameMode crawl --setDungeonRng 0 --setMainRng 0
 
 nodeBenchBattle:
-	node dist/build/LambdaHack/LambdaHack.jsexe/all.js --dbgMsgSer --newGame 3 --noAnim --maxFps 100000 --frontendNull --benchmark --stopAfterFrames 1000 --automateAll --keepAutomated --gameMode battle --setDungeonRng 0 --setMainRng 0
+	node dist/build/LambdaHack/LambdaHack.jsexe/all.js --dbgMsgSer --logPriority 4 --newGame 3 --noAnim --maxFps 100000 --frontendNull --benchmark --stopAfterFrames 1000 --automateAll --keepAutomated --gameMode battle --setDungeonRng 0 --setMainRng 0
 
 nodeBench: nodeBenchBattle nodeBenchCrawl
 
 
-test-travis-short: test-short
-
 test-travis: test-short test-medium benchNull
 
 test: test-short test-medium benchNull
@@ -103,70 +101,73 @@
 test-medium: testRaid-medium testBrawl-medium testShootout-medium testEscape-medium testZoo-medium testAmbush-medium testCrawl-medium testSafari-medium testSafariSurvival-medium testBattle-medium testBattleSurvival-medium
 
 testRaid-medium:
-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --boostRandomItem --newGame 5 --maxFps 100000 --frontendTeletype --benchmark --stopAfterSeconds 20 --dumpInitRngs --automateAll --keepAutomated --gameMode raid 2> /tmp/teletypetest.log
+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --logPriority 4 --boostRandomItem --newGame 5 --maxFps 100000 --frontendTeletype --benchmark --stopAfterSeconds 20 --dumpInitRngs --automateAll --keepAutomated --gameMode raid 2> /tmp/teletypetest.log
 
 testBrawl-medium:
-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --boostRandomItem --newGame 5 --maxFps 100000 --frontendTeletype --benchmark --stopAfterSeconds 20 --dumpInitRngs --automateAll --keepAutomated --gameMode brawl 2> /tmp/teletypetest.log
+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --logPriority 4 --boostRandomItem --newGame 5 --maxFps 100000 --frontendTeletype --benchmark --stopAfterSeconds 20 --dumpInitRngs --automateAll --keepAutomated --gameMode brawl 2> /tmp/teletypetest.log
 
 testShootout-medium:
-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --boostRandomItem --newGame 5 --maxFps 100000 --frontendTeletype --benchmark --stopAfterSeconds 20 --dumpInitRngs --automateAll --keepAutomated --gameMode shootout 2> /tmp/teletypetest.log
+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --logPriority 4 --boostRandomItem --newGame 5 --maxFps 100000 --frontendTeletype --benchmark --stopAfterSeconds 20 --dumpInitRngs --automateAll --keepAutomated --gameMode shootout 2> /tmp/teletypetest.log
 
 testEscape-medium:
-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --boostRandomItem --newGame 3 --maxFps 100000 --frontendTeletype --benchmark --stopAfterSeconds 40 --dumpInitRngs --automateAll --keepAutomated --gameMode escape 2> /tmp/teletypetest.log
+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --logPriority 4 --boostRandomItem --newGame 3 --maxFps 100000 --frontendTeletype --benchmark --stopAfterSeconds 40 --dumpInitRngs --automateAll --keepAutomated --gameMode escape 2> /tmp/teletypetest.log
 
 testZoo-medium:
-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --boostRandomItem --newGame 2 --maxFps 100000 --frontendTeletype --benchmark --stopAfterSeconds 100 --dumpInitRngs --automateAll --keepAutomated --gameMode zoo 2> /tmp/teletypetest.log
+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --logPriority 4 --boostRandomItem --newGame 2 --maxFps 100000 --frontendTeletype --benchmark --stopAfterSeconds 100 --dumpInitRngs --automateAll --keepAutomated --gameMode zoo 2> /tmp/teletypetest.log
 
 testAmbush-medium:
-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --boostRandomItem --newGame 5 --noAnim --maxFps 100000 --frontendTeletype --benchmark --stopAfterSeconds 20 --dumpInitRngs --automateAll --keepAutomated --gameMode ambush 2> /tmp/teletypetest.log
+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --logPriority 4 --boostRandomItem --newGame 5 --noAnim --maxFps 100000 --frontendTeletype --benchmark --stopAfterSeconds 20 --dumpInitRngs --automateAll --keepAutomated --gameMode ambush 2> /tmp/teletypetest.log
 
 testCrawl-medium:
-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --boostRandomItem --newGame 1 --noAnim --maxFps 100000 --frontendTeletype --benchmark --stopAfterSeconds 200 --dumpInitRngs --automateAll --keepAutomated --gameMode crawl 2> /tmp/teletypetest.log
+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --logPriority 4 --boostRandomItem --newGame 1 --noAnim --maxFps 100000 --frontendTeletype --benchmark --stopAfterSeconds 200 --dumpInitRngs --automateAll --keepAutomated --gameMode crawl 2> /tmp/teletypetest.log
 
 testSafari-medium:
-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --boostRandomItem --newGame 2 --noAnim --maxFps 100000 --frontendTeletype --benchmark --stopAfterSeconds 100 --dumpInitRngs --automateAll --keepAutomated --gameMode safari 2> /tmp/teletypetest.log
+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --logPriority 4 --boostRandomItem --newGame 2 --noAnim --maxFps 100000 --frontendTeletype --benchmark --stopAfterSeconds 100 --dumpInitRngs --automateAll --keepAutomated --gameMode safari 2> /tmp/teletypetest.log
 
 testSafariSurvival-medium:
-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --boostRandomItem --newGame 8 --noAnim --maxFps 100000 --frontendTeletype --benchmark --stopAfterSeconds 60 --dumpInitRngs --automateAll --keepAutomated --gameMode "safari survival" 2> /tmp/teletypetest.log
+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --logPriority 4 --boostRandomItem --newGame 8 --noAnim --maxFps 100000 --frontendTeletype --benchmark --stopAfterSeconds 60 --dumpInitRngs --automateAll --keepAutomated --gameMode "safari survival" 2> /tmp/teletypetest.log
 
 testBattle-medium:
-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --boostRandomItem --newGame 3 --noAnim --maxFps 100000 --frontendTeletype --benchmark --stopAfterSeconds 20 --dumpInitRngs --automateAll --keepAutomated --gameMode battle 2> /tmp/teletypetest.log
+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --logPriority 4 --boostRandomItem --newGame 3 --noAnim --maxFps 100000 --frontendTeletype --benchmark --stopAfterSeconds 20 --dumpInitRngs --automateAll --keepAutomated --gameMode battle 2> /tmp/teletypetest.log
 
 testBattleSurvival-medium:
-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --boostRandomItem --newGame 7 --noAnim --maxFps 100000 --frontendTeletype --benchmark --stopAfterSeconds 60 --dumpInitRngs --automateAll --keepAutomated --gameMode "battle survival" 2> /tmp/teletypetest.log
+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --logPriority 4 --boostRandomItem --newGame 7 --noAnim --maxFps 100000 --frontendTeletype --benchmark --stopAfterSeconds 60 --dumpInitRngs --automateAll --keepAutomated --gameMode "battle survival" 2> /tmp/teletypetest.log
 
 testDefense-medium:
-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --boostRandomItem --newGame 9 --noAnim --maxFps 100000 --frontendTeletype --benchmark --stopAfterSeconds 300 --dumpInitRngs --automateAll --keepAutomated --gameMode defense 2> /tmp/teletypetest.log
+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --logPriority 4 --boostRandomItem --newGame 9 --noAnim --maxFps 100000 --frontendTeletype --benchmark --stopAfterSeconds 300 --dumpInitRngs --automateAll --keepAutomated --gameMode defense 2> /tmp/teletypetest.log
 
 test-short-new:
-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --boostRandomItem --newGame 5 --savePrefix raid --dumpInitRngs --automateAll --keepAutomated --gameMode raid --frontendTeletype --stopAfterSeconds 2 2> /tmp/teletypetest.log
-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --boostRandomItem --newGame 5 --savePrefix brawl --dumpInitRngs --automateAll --keepAutomated --gameMode brawl --frontendTeletype --stopAfterSeconds 2 2> /tmp/teletypetest.log
-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --boostRandomItem --newGame 5 --savePrefix shootout --dumpInitRngs --automateAll --keepAutomated --gameMode shootout --frontendTeletype --stopAfterSeconds 2 2> /tmp/teletypetest.log
-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --boostRandomItem --newGame 5 --savePrefix escape --dumpInitRngs --automateAll --keepAutomated --gameMode escape --frontendTeletype --stopAfterSeconds 2 2> /tmp/teletypetest.log
-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --boostRandomItem --newGame 5 --savePrefix zoo --dumpInitRngs --automateAll --keepAutomated --gameMode zoo --frontendTeletype --stopAfterSeconds 2 2> /tmp/teletypetest.log
-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --boostRandomItem --newGame 5 --savePrefix ambush --dumpInitRngs --automateAll --keepAutomated --gameMode ambush --frontendTeletype --stopAfterSeconds 2 2> /tmp/teletypetest.log
-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --boostRandomItem --newGame 5 --savePrefix crawl --dumpInitRngs --automateAll --keepAutomated --gameMode crawl --frontendTeletype --stopAfterSeconds 2 2> /tmp/teletypetest.log
-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --boostRandomItem --newGame 5 --savePrefix safari --dumpInitRngs --automateAll --keepAutomated --gameMode safari --frontendTeletype --stopAfterSeconds 2 2> /tmp/teletypetest.log
-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --boostRandomItem --newGame 5 --savePrefix safariSurvival --dumpInitRngs --automateAll --keepAutomated --gameMode "safari survival" --frontendTeletype --stopAfterSeconds 2 2> /tmp/teletypetest.log
-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --boostRandomItem --newGame 5 --savePrefix battle --dumpInitRngs --automateAll --keepAutomated --gameMode battle --frontendTeletype --stopAfterSeconds 2 2> /tmp/teletypetest.log
-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --boostRandomItem --newGame 5 --savePrefix battleSurvival --dumpInitRngs --automateAll --keepAutomated --gameMode "battle survival" --frontendTeletype --stopAfterSeconds 2 2> /tmp/teletypetest.log
+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --logPriority 4 --boostRandomItem --newGame 5 --savePrefix raid --dumpInitRngs --automateAll --keepAutomated --gameMode raid --frontendTeletype --stopAfterSeconds 2 2> /tmp/teletypetest.log
+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --logPriority 4 --boostRandomItem --newGame 5 --savePrefix brawl --dumpInitRngs --automateAll --keepAutomated --gameMode brawl --frontendTeletype --stopAfterSeconds 2 2> /tmp/teletypetest.log
+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --logPriority 4 --boostRandomItem --newGame 5 --savePrefix shootout --dumpInitRngs --automateAll --keepAutomated --gameMode shootout --frontendTeletype --stopAfterSeconds 2 2> /tmp/teletypetest.log
+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --logPriority 4 --boostRandomItem --newGame 5 --savePrefix escape --dumpInitRngs --automateAll --keepAutomated --gameMode escape --frontendTeletype --stopAfterSeconds 2 2> /tmp/teletypetest.log
+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --logPriority 4 --boostRandomItem --newGame 5 --savePrefix zoo --dumpInitRngs --automateAll --keepAutomated --gameMode zoo --frontendTeletype --stopAfterSeconds 2 2> /tmp/teletypetest.log
+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --logPriority 4 --boostRandomItem --newGame 5 --savePrefix ambush --dumpInitRngs --automateAll --keepAutomated --gameMode ambush --frontendTeletype --stopAfterSeconds 2 2> /tmp/teletypetest.log
+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --logPriority 4 --boostRandomItem --newGame 5 --savePrefix crawl --dumpInitRngs --automateAll --keepAutomated --gameMode crawl --frontendTeletype --stopAfterSeconds 2 2> /tmp/teletypetest.log
+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --logPriority 4 --boostRandomItem --newGame 5 --savePrefix safari --dumpInitRngs --automateAll --keepAutomated --gameMode safari --frontendTeletype --stopAfterSeconds 2 2> /tmp/teletypetest.log
+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --logPriority 4 --boostRandomItem --newGame 5 --savePrefix safariSurvival --dumpInitRngs --automateAll --keepAutomated --gameMode "safari survival" --frontendTeletype --stopAfterSeconds 2 2> /tmp/teletypetest.log
+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --logPriority 4 --boostRandomItem --newGame 5 --savePrefix battle --dumpInitRngs --automateAll --keepAutomated --gameMode battle --frontendTeletype --stopAfterSeconds 2 2> /tmp/teletypetest.log
+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --logPriority 4 --boostRandomItem --newGame 5 --savePrefix battleSurvival --dumpInitRngs --automateAll --keepAutomated --gameMode "battle survival" --frontendTeletype --stopAfterSeconds 2 2> /tmp/teletypetest.log
 
 # "--setDungeonRng 0 --setMainRng 0" is needed for determinism relative to seed
 # generated before game save
 test-short-load:
-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --boostRandomItem --savePrefix raid --dumpInitRngs --automateAll --keepAutomated --gameMode raid --frontendTeletype --stopAfterSeconds 2 --setDungeonRng 0 --setMainRng 0 2> /tmp/teletypetest.log
-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --boostRandomItem --savePrefix brawl --dumpInitRngs --automateAll --keepAutomated --gameMode brawl --frontendTeletype --stopAfterSeconds 2 --setDungeonRng 0 --setMainRng 0 2> /tmp/teletypetest.log
-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --boostRandomItem --savePrefix shootout --dumpInitRngs --automateAll --keepAutomated --gameMode shootouti --frontendTeletype --stopAfterSeconds 2 --setDungeonRng 0 --setMainRng 0 2> /tmp/teletypetest.log
-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --boostRandomItem --savePrefix escape --dumpInitRngs --automateAll --keepAutomated --gameMode escape --frontendTeletype --stopAfterSeconds 2 --setDungeonRng 0 --setMainRng 0 2> /tmp/teletypetest.log
-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --boostRandomItem --savePrefix zoo --dumpInitRngs --automateAll --keepAutomated --gameMode zoo --frontendTeletype --stopAfterSeconds 2 --setDungeonRng 0 --setMainRng 0 2> /tmp/teletypetest.log
-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --boostRandomItem --savePrefix ambush --dumpInitRngs --automateAll --keepAutomated --gameMode ambush --frontendTeletype --stopAfterSeconds 2 --setDungeonRng 0 --setMainRng 0 2> /tmp/teletypetest.log
-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --boostRandomItem --savePrefix crawl --dumpInitRngs --automateAll --keepAutomated --gameMode crawl --frontendTeletype --stopAfterSeconds 2 --setDungeonRng 0 --setMainRng 0 2> /tmp/teletypetest.log
-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --boostRandomItem --savePrefix safari --dumpInitRngs --automateAll --keepAutomated --gameMode safari --frontendTeletype --stopAfterSeconds 2 --setDungeonRng 0 --setMainRng 0 2> /tmp/teletypetest.log
-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --boostRandomItem --savePrefix safariSurvival --dumpInitRngs --automateAll --keepAutomated --gameMode "safari survival" --frontendTeletype --stopAfterSeconds 2 --setDungeonRng 0 --setMainRng 0 2> /tmp/teletypetest.log
-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --boostRandomItem --savePrefix battle --dumpInitRngs --automateAll --keepAutomated --gameMode battle --frontendTeletype --stopAfterSeconds 2 --setDungeonRng 0 --setMainRng 0 2> /tmp/teletypetest.log
-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --boostRandomItem --savePrefix battleSurvival --dumpInitRngs --automateAll --keepAutomated --gameMode "battle survival" --frontendTeletype --stopAfterSeconds 2 --setDungeonRng 0 --setMainRng 0 2> /tmp/teletypetest.log
+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --logPriority 4 --boostRandomItem --savePrefix raid --dumpInitRngs --automateAll --keepAutomated --gameMode raid --frontendTeletype --stopAfterSeconds 2 --setDungeonRng 0 --setMainRng 0 2> /tmp/teletypetest.log
+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --logPriority 4 --boostRandomItem --savePrefix brawl --dumpInitRngs --automateAll --keepAutomated --gameMode brawl --frontendTeletype --stopAfterSeconds 2 --setDungeonRng 0 --setMainRng 0 2> /tmp/teletypetest.log
+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --logPriority 4 --boostRandomItem --savePrefix shootout --dumpInitRngs --automateAll --keepAutomated --gameMode shootouti --frontendTeletype --stopAfterSeconds 2 --setDungeonRng 0 --setMainRng 0 2> /tmp/teletypetest.log
+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --logPriority 4 --boostRandomItem --savePrefix escape --dumpInitRngs --automateAll --keepAutomated --gameMode escape --frontendTeletype --stopAfterSeconds 2 --setDungeonRng 0 --setMainRng 0 2> /tmp/teletypetest.log
+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --logPriority 4 --boostRandomItem --savePrefix zoo --dumpInitRngs --automateAll --keepAutomated --gameMode zoo --frontendTeletype --stopAfterSeconds 2 --setDungeonRng 0 --setMainRng 0 2> /tmp/teletypetest.log
+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --logPriority 4 --boostRandomItem --savePrefix ambush --dumpInitRngs --automateAll --keepAutomated --gameMode ambush --frontendTeletype --stopAfterSeconds 2 --setDungeonRng 0 --setMainRng 0 2> /tmp/teletypetest.log
+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --logPriority 4 --boostRandomItem --savePrefix crawl --dumpInitRngs --automateAll --keepAutomated --gameMode crawl --frontendTeletype --stopAfterSeconds 2 --setDungeonRng 0 --setMainRng 0 2> /tmp/teletypetest.log
+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --logPriority 4 --boostRandomItem --savePrefix safari --dumpInitRngs --automateAll --keepAutomated --gameMode safari --frontendTeletype --stopAfterSeconds 2 --setDungeonRng 0 --setMainRng 0 2> /tmp/teletypetest.log
+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --logPriority 4 --boostRandomItem --savePrefix safariSurvival --dumpInitRngs --automateAll --keepAutomated --gameMode "safari survival" --frontendTeletype --stopAfterSeconds 2 --setDungeonRng 0 --setMainRng 0 2> /tmp/teletypetest.log
+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --logPriority 4 --boostRandomItem --savePrefix battle --dumpInitRngs --automateAll --keepAutomated --gameMode battle --frontendTeletype --stopAfterSeconds 2 --setDungeonRng 0 --setMainRng 0 2> /tmp/teletypetest.log
+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --logPriority 4 --boostRandomItem --savePrefix battleSurvival --dumpInitRngs --automateAll --keepAutomated --gameMode "battle survival" --frontendTeletype --stopAfterSeconds 2 --setDungeonRng 0 --setMainRng 0 2> /tmp/teletypetest.log
 
 
+version:
+	dist/build/LambdaHack/LambdaHack --version
+
 build-binary-common:
 	cabal install --disable-library-profiling --disable-profiling --disable-documentation -f-release --only-dependencies
 	cabal configure --disable-library-profiling --disable-profiling -f-release --prefix=/ --datadir=. --datasubdir=.
@@ -189,7 +190,9 @@
 
 build-binary: build-binary-common
 	cp LambdaHackTheGameInstall/bin/LambdaHack LambdaHackTheGame
-	tar -czf LambdaHack_x_ubuntu-16.04-amd64.tar.gz LambdaHackTheGame
+	dist/build/LambdaHack/LambdaHack --version > /dev/null; \
+	LH_VERSION=$$(cat ~/.LambdaHack/stdout.txt); \
+	tar -czf LambdaHack_$${LH_VERSION}_ubuntu-16.04-amd64.tar.gz LambdaHackTheGame
 
 new-build-dev:
 	cabal new-build --datadir=. --disable-optimization -j1
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -75,14 +75,16 @@
 you may prefer to use a native binary for your architecture, if it exists.
 
 Pre-compiled game binaries for some platforms are available through
-the release page[11] and from AppVeyor (Windows 32bit[17] and Windows 64bit[18];
-note that these no longer work on Windows XP, since Cygwin and MSYS2
-dropped support for XP). To use a pre-compiled binary archive,
+the release page[11] (and continuously from AppVeyor[18]).
+Note that Windows binaries no longer work on Windows XP, since Cygwin
+and MSYS2 dropped support for XP). To use a pre-compiled binary archive,
 unpack it and run the executable in the unpacked directory.
+Or use program shortcuts from the installer, if available.
 
-On Linux, make sure you have the SDL2 libraries suite installed on your system
-(e.g., libsdl2, libsdl2-ttf). For Windows, the SDL2 and all other needed
-libraries are already contained in the game's binary archive.
+On Linux, make sure you have the SDL2 libraries installed on your system
+(e.g., libsdl2-2.0-0, libsdl2-ttf-2.0-0 on Ubuntu; also libdw1).
+For Windows, the SDL2 and all other needed libraries are already contained
+in the game's binary archive.
 
 
 Screen and keyboard configuration
@@ -246,5 +248,4 @@
 [11]: https://github.com/LambdaHack/LambdaHack/releases/latest
 [15]: https://github.com/ghcjs/ghcjs
 [16]: https://www.npmjs.com/package/google-closure-compiler
-[17]: https://ci.appveyor.com/project/Mikolaj/lambdahack-4hh0j/build/artifacts
 [18]: https://ci.appveyor.com/project/Mikolaj/lambdahack/build/artifacts
