diff --git a/CursesUI.hs b/CursesUI.hs
--- a/CursesUI.hs
+++ b/CursesUI.hs
@@ -162,8 +162,19 @@
             ]
         bindingsTable IMPlay GravRight =
             [ (-7, BindingsEntry "help" [CmdHelp])
+            , (-6, BindingsEntry "bind" [CmdBind Nothing])
             , (7, BindingsEntry "quit" [CmdQuit])
             ]
+        bindingsTable IMReplay GravLeft =
+            [ ( 0, BindingsEntry "wait" [CmdWait])
+            , ( 4, BindingsEntry "undo, redo" [CmdUndo, CmdRedo])
+            , ( 5, BindingsEntry "marks" [CmdMark, CmdJumpMark, CmdReset])
+            ]
+        bindingsTable IMReplay GravRight =
+            [ (-7, BindingsEntry "help" [CmdHelp])
+            , (-6, BindingsEntry "bind" [CmdBind Nothing])
+            , (7, BindingsEntry "quit" [CmdQuit])
+            ]
         bindingsTable IMEdit GravLeft =
             [ (-4, BindingsEntry "move" $ map (CmdDir WHSSelected) hexDirs)
             , (-3, BindingsEntry "rotate"
@@ -176,6 +187,7 @@
             ]
         bindingsTable IMEdit GravRight =
             [ (-7, BindingsEntry "help" [CmdHelp])
+            , (-6, BindingsEntry "bind" [CmdBind Nothing])
             , (-4, BindingsEntry "place" $ map CmdTile
                 [ BlockTile []
                 , SpringTile Relaxed zero
diff --git a/CursesUIMInstance.hs b/CursesUIMInstance.hs
--- a/CursesUIMInstance.hs
+++ b/CursesUIMInstance.hs
@@ -213,6 +213,8 @@
         else HelpDone
 showHelpPaged' from IMMeta HelpPageGame =
     drawBasicHelpPage from ("INTRICACY",magenta) (metagameHelpText,magenta)
+showHelpPaged' from IMMeta (HelpPageInitiated n) =
+    drawBasicHelpPage from ("Initiation complete",magenta) (initiationHelpText n,magenta)
 showHelpPaged' from IMEdit HelpPageFirstEdit =
     drawBasicHelpPage from ("Your first lock:",magenta) (firstEditHelpText,green)
 showHelpPaged' _ _ _ = return HelpNone
@@ -442,6 +444,7 @@
     getUIMousePos = return Nothing
     setYNButtons = return ()
     onNewMode _ = say ""
+    withNoBG = id
 
     toggleColourMode = modify $ \s -> s {monochrome = not $ monochrome s}
 
diff --git a/Debug.hs b/Debug.hs
deleted file mode 100644
--- a/Debug.hs
+++ /dev/null
@@ -1,19 +0,0 @@
--- This file is part of Intricacy
--- Copyright (C) 2013 Martin Bays <mbays@sdf.org>
---
--- This program is free software: you can redistribute it and/or modify
--- it under the terms of version 3 of the GNU General Public License as
--- published by the Free Software Foundation, or any later version.
---
--- You should have received a copy of the GNU General Public License
--- along with this program.  If not, see http://www.gnu.org/licenses/.
-
-module Debug where
-
-import qualified Debug.Trace as Trace
-import Text.Show.Pretty (ppShow)
-
-prettyTrace :: Show a => a -> b -> b
-prettyTrace = Trace.trace . ppShow
-prettyTraceVal :: Show a => a -> a
-prettyTraceVal x = prettyTrace x x
diff --git a/Interact.hs b/Interact.hs
--- a/Interact.hs
+++ b/Interact.hs
@@ -120,7 +120,7 @@
 processCommand im CmdQuit = do
     case im of
 	IMReplay -> throwE $ InteractSuccess False
-	IMPlay -> lift (or <$> sequence [gets psIsSub, null <$> gets psGameStateMoveStack])
+	IMPlay -> lift (or <$> sequence [gets psIsSub, gets psSaved, null <$> gets psGameStateMoveStack])
 	    >>? throwE $ InteractSuccess False
 	IMEdit -> lift editStateUnsaved >>! throwE $ InteractSuccess True
 	_ -> return ()
@@ -146,7 +146,7 @@
             first <- not <$> liftIO hasLocks
             return $ [HelpPageInput] ++ if first then [HelpPageFirstEdit] else []
         _ -> return [HelpPageInput]
-    let showPage p = showHelp im p >>? do
+    let showPage p = withNoBG $ showHelp im p >>? do
 	    void $ textInput "[press a key or RMB]" 1 False True Nothing Nothing
     sequence_ $ map showPage helpPages
 processCommand' im (CmdBind mcmd)= lift $ (>> endPrompt) $ runMaybeT $ do
@@ -168,14 +168,17 @@
     when (im == IMMeta) $ modify $ \ms -> ms { retiredLocks = Nothing }
 processCommand' im CmdMark = void.runMaybeT $ do
     guard $ im `elem` [IMEdit, IMPlay, IMReplay]
-    str <- MaybeT $ lift $ textInput "Mark: " 1 False True Nothing Nothing
+    str <- MaybeT $ lift $ textInput "Type character for mark: "
+        1 False True Nothing Nothing
     ch <- liftMaybe $ listToMaybe str
     guard $ ch `notElem` [startMark, '\'']
     lift $ setMark True ch
 processCommand' im CmdJumpMark = void.runMaybeT $ do
     guard $ im `elem` [IMEdit, IMPlay, IMReplay]
+    marks <- lift marksSet
     str <- MaybeT $ lift $ textInput
-	"Jump to mark (\"'\" to unjump): " 1 False True Nothing Nothing
+	("Jump to mark [" ++ intersperse ',' marks ++ "]: ")
+            1 False True (Just $ map (:[]) marks) Nothing
     ch <- liftMaybe $ listToMaybe str
     lift $ jumpMark ch
 processCommand' im CmdReset = jumpMark startMark
@@ -239,34 +242,40 @@
 		modify $ \ms -> ms {curAuth = Nothing}
 	    , do
 		confirmOrBail "Reset password?"
-		passwd <- inputPassword regName "Enter new password:"
-		lift $ do
-		    resp <- curServerAction $ ResetPassword passwd
-		    case resp of
-			ServerAck -> do
-			    lift $ drawMessage "New password set."
-			    modify $ \ms -> ms {curAuth = Just $ Auth regName passwd}
-			ServerError err -> lift $ drawError err
-			_ -> lift $ drawMessage $ "Bad server response: " ++ show resp
+                void.lift.runMaybeT $ do
+                    passwd <- inputPassword regName True "Enter new password:"
+                    lift $ do
+                        resp <- curServerAction $ ResetPassword passwd
+                        case resp of
+                            ServerAck -> do
+                                lift $ drawMessage "New password set."
+                                modify $ \ms -> ms {curAuth = Just $ Auth regName passwd}
+                            ServerError err -> lift $ drawError err
+                            _ -> lift $ drawMessage $ "Bad server response: " ++ show resp
 	    , do
 		confirmOrBail "Configure email notifications?"
 		setNotifications
 	    ]
-	else do
-	    passwd <- inputPassword regName "Enter new password:"
-	    lift $ do
-		modify $ \ms -> ms {curAuth = Just $ Auth regName passwd}
-		resp <- curServerAction Register
-		case resp of
-		    ServerAck -> do
-			invalidateUInfo regName
-			refreshUInfoUI
-			conf <- lift $ confirm "Registered! Would you like to be notified by email when someone solves your lock?"
-			if conf then void $ runMaybeT setNotifications else lift $ drawMessage "Notifications disabled."
-		    ServerError err -> do
-			lift $ drawError err
-			modify $ \ms -> ms {curAuth = Nothing}
-		    _ -> lift $ drawMessage $ "Bad server response: " ++ show resp
+	else msum [ do
+                mgetUInfo regName
+                lift.lift $ drawError "Sorry, this codename is already taken."
+            , do
+                confirmOrBail $ "Register codename " ++ regName ++ "?"
+                passwd <- inputPassword regName True "Enter new password:"
+                lift $ do
+                    modify $ \ms -> ms {curAuth = Just $ Auth regName passwd}
+                    resp <- curServerAction Register
+                    case resp of
+                        ServerAck -> do
+                            invalidateUInfo regName
+                            refreshUInfoUI
+                            conf <- lift $ confirm "Registered! Would you like to be notified by email when someone solves your lock?"
+                            if conf then void $ runMaybeT setNotifications else lift $ drawMessage "Notifications disabled."
+                        ServerError err -> do
+                            lift $ drawError err
+                            modify $ \ms -> ms {curAuth = Nothing}
+                        _ -> lift $ drawMessage $ "Bad server response: " ++ show resp
+            ]
 
     where setNotifications = do
 	    address <- MaybeT $ lift $ textInput "Enter address, or leave blank to disable notifications:" 128 False False Nothing Nothing
@@ -285,7 +294,7 @@
 	modify $ \ms -> ms {curAuth = Nothing}
     else do
 	name <- mgetCurName
-	passwd <- inputPassword name $ "Enter password for "++name++":"
+	passwd <- inputPassword name False $ "Enter password for "++name++":"
 	lift $ do
 	    modify $ \ms -> ms {curAuth = Just $ Auth name passwd}
 	    resp <- curServerAction $ Authenticate
@@ -441,7 +450,7 @@
 	    ]
 	return (baseLock size, Nothing)
     not <$> liftIO hasLocks >>? do
-        lift.lift $ showHelp IMEdit HelpPageFirstEdit >>? do
+        lift.lift $ withNoBG $ showHelp IMEdit HelpPageFirstEdit >>? do
             void $ textInput
                 "[Press a key or RMB to continue; you can review this help later with '?']"
                 1 False True Nothing Nothing
@@ -476,13 +485,14 @@
 		rbdg <- lift $ getUIBinding IMMeta (CmdRegister False)
 		if isNothing mauth
 		    then do
-			let showPage p = lift $ showHelp IMMeta p >>? do
-				void $ textInput
-				    "[Tutorial completed! Press a key or RMB to continue; you can review this help later with '?']"
-				    1 False True Nothing Nothing
-			showPage HelpPageGame
+			let showPage p prompt = lift $ withNoBG $ showHelp IMMeta p >>? do
+				void $ textInput prompt 1 False True Nothing Nothing
+                        showPage (HelpPageInitiated 1) "[Tutorial complete! Press a key or RMB to continue]"
+                        showPage (HelpPageInitiated 2) "[Press a key or RMB to continue]"
+                        showPage (HelpPageInitiated 3) "[Press a key or RMB to continue]"
+			--showPage HelpPageGame "[Press a key or RMB to continue; you can review this information later with '?']"
 			lift $ drawMessage $ 
-			    "Tutorial completed! To play on the server, pick a codename ('"++cbdg++
+			    "To join the game: pick a codename ('"++cbdg++
 			    "') and register it ('"++rbdg++"')."
 		    else lift $ drawMessage $ "Tutorial completed!"
     
@@ -534,14 +544,24 @@
     board <- stateBoard <$> gets psCurrentState
     wsel <- gets wrenchSelected
     void.runMaybeT $ do
-	tile <- liftMaybe $ snd <$> Map.lookup pos board
-	guard $ case tile of {WrenchTile _ -> True; HookTile -> True; _ -> False}
+	tp <- liftMaybe $ tileType . snd <$> Map.lookup pos board
+        msum [ guard $ tp == HookTile
+            , do
+                guard $ tp == WrenchTile zero
+                board' <- lift $ stateBoard . fst . runWriter . physicsTick (WrenchPush dir) <$> gets psCurrentState
+                msum $ [ do
+                        tp' <- liftMaybe $ tileType . snd <$> Map.lookup pos' board'
+                        guard $ tp' == WrenchTile zero
+                    | d <- [0,1,2]
+                    , let pos' = d *^ dir +^ pos ]
+                    ++ [ (lift.lift $ warpPointer pos) >> mzero ]
+            ]
 	lift $ processCommand' IMPlay $ CmdDir WHSSelected $ dir
 	board' <- stateBoard <$> gets psCurrentState
 	msum [ do
-		tile' <- liftMaybe $ snd <$> Map.lookup pos' board'
-		guard $ tileType tile' == if wsel then WrenchTile zero else HookTile
-		lift.lift $ warpPointer $ pos'
+		tp' <- liftMaybe $ tileType . snd <$> Map.lookup pos' board'
+		guard $ tp' == if wsel then WrenchTile zero else HookTile
+		lift.lift $ warpPointer pos'
 	    | pos' <- map (+^pos) $ hexDisc 2 ]
 
 processCommand' IMPlay cmd = do
@@ -719,10 +739,15 @@
 	    Just err -> lift $ drawError $ "Write failed: "++err
 processCommand' _ _ = return ()
 
-inputPassword :: UIMonad uiM => Codename -> String -> MaybeT (MainStateT uiM) String
-inputPassword name prompt = do
+inputPassword :: UIMonad uiM => Codename -> Bool -> String -> MaybeT (MainStateT uiM) String
+inputPassword name confirm prompt = do
     pw <- MaybeT $ lift $ textInput prompt 64 True False Nothing Nothing
     guard $ not $ null pw
+    when confirm $ do
+        pw' <- MaybeT $ lift $ textInput "Confirm password:" 64 True False Nothing Nothing
+        when (pw /= pw') $ do
+            lift.lift $ drawError "Passwords don't match!"
+            mzero
     RCPublicKey publicKey <- MaybeT $ getFreshRecBlocking RecPublicKey
     encryptPassword publicKey name pw
 
@@ -756,20 +781,20 @@
 
 subPlay :: UIMonad uiM => Lock -> MainStateT uiM ()
 subPlay lock =
-    pushEState =<< psCurrentState <$> (execSubMainState $ newPlayState lock Nothing False True)
+    pushEState =<< psCurrentState <$> (execSubMainState $ newPlayState lock Nothing False True False)
 
 solveLock,solveLockTut :: UIMonad uiM => Lock -> Maybe String -> MaybeT (MainStateT uiM) Solution
 solveLock = solveLock' False
 solveLockTut = solveLock' True
 solveLock' isTut lock title = do
-    (InteractSuccess solved, ps) <- lift $ runSubMainState $ newPlayState (reframe lock) title isTut False
+    (InteractSuccess solved, ps) <- lift $ runSubMainState $ newPlayState (reframe lock) title isTut False False
     guard $ solved
     return $ reverse $ (map snd) $ psGameStateMoveStack ps
 
 solveLockSaving :: UIMonad uiM => LockSpec -> Maybe SavedPlayState -> Bool -> Lock -> Maybe String -> MaybeT (MainStateT uiM) Solution
 solveLockSaving ls msps isTut lock title = do
     (InteractSuccess solved, ps) <- lift $ runSubMainState $
-	((maybe newPlayState restorePlayState) msps) (reframe lock) title isTut False
+	((maybe newPlayState restorePlayState) msps) (reframe lock) title isTut False True
     if solved
 	then do
 	    unless isTut $ lift $ modify $ \ms -> ms { partialSolutions = Map.delete ls $ partialSolutions ms }
diff --git a/InteractUtil.hs b/InteractUtil.hs
--- a/InteractUtil.hs
+++ b/InteractUtil.hs
@@ -130,6 +130,15 @@
 
 startMark = '^'
 
+marksSet :: UIMonad uiM => MainStateT uiM [Char]
+marksSet = do
+    mst <- get
+    return $ case ms2im mst of
+        IMEdit -> Map.keys $ esMarks mst
+        IMPlay -> Map.keys $ psMarks mst
+        IMReplay -> Map.keys $ rsMarks mst
+        _ -> []
+
 jumpMark :: UIMonad uiM => Char -> MainStateT uiM ()
 jumpMark ch = do
     mst <- get
diff --git a/MainState.hs b/MainState.hs
--- a/MainState.hs
+++ b/MainState.hs
@@ -67,6 +67,7 @@
     getUIMousePos :: m (Maybe HexPos)
     setYNButtons :: m ()
     onNewMode :: InputMode -> m ()
+    withNoBG :: m () -> m ()
     suspend,redraw :: m ()
 
     doUI :: m a -> IO (Maybe a)
@@ -87,6 +88,7 @@
 	, psTitle::Maybe String
 	, psIsTut::Bool
 	, psIsSub::Bool
+	, psSaved::Bool
 	, psMarks::Map Char MainState
 	}
     | ReplayState
@@ -132,8 +134,8 @@
 
 type MainStateT = StateT MainState
 
-data HelpPage = HelpPageInput | HelpPageGame | HelpPageFirstEdit
-    deriving (Eq, Ord, Show, Enum)
+data HelpPage = HelpPageInput | HelpPageGame | HelpPageInitiated Int | HelpPageFirstEdit
+    deriving (Eq, Ord, Show)
 
 ms2im :: MainState -> InputMode
 ms2im mainSt = case mainSt of
@@ -142,7 +144,7 @@
     EditState {} -> IMEdit
     MetaState {} -> IMMeta
 
-newPlayState (frame,st) title isTut sub = PlayState st frame [] False False [] [] title isTut sub Map.empty
+newPlayState (frame,st) title isTut sub saved = PlayState st frame [] False False [] [] title isTut sub saved Map.empty
 newReplayState st soln title = ReplayState st [] soln [] title Map.empty
 newEditState (frame,st) msoln mpath = EditState [st] [] frame mpath
     ((\s->(st,s))<$>msoln) (Just (st, isJust msoln)) Nothing (PHS zero) (PHS zero) Map.empty
@@ -169,12 +171,12 @@
 savePlayState ps = SavedPlayState (getMoves ps) $ Map.map getMoves $ psMarks ps
     where getMoves = reverse . map snd . psGameStateMoveStack
 
-restorePlayState :: SavedPlayState -> Lock -> (Maybe String) -> Bool -> Bool -> MainState
-restorePlayState (SavedPlayState pms markPMs) (frame,st) title isTut sub =
+restorePlayState :: SavedPlayState -> Lock -> (Maybe String) -> Bool -> Bool -> Bool -> MainState
+restorePlayState (SavedPlayState pms markPMs) (frame,st) title isTut sub saved =
     (stateAfterMoves pms) { psMarks = Map.map stateAfterMoves markPMs }
     where
 	stateAfterMoves pms = let (stack,st') = applyMoves st pms
-	    in (newPlayState (frame, st') title isTut sub) { psGameStateMoveStack = stack }
+	    in (newPlayState (frame, st') title isTut sub saved) { psGameStateMoveStack = stack }
 	applyMoves st pms = foldl tick ([],st) pms
 	tick :: ([(GameState,PlayerMove)],GameState) -> PlayerMove -> ([(GameState,PlayerMove)],GameState)
 	tick (stack,st) pm = ((st,pm):stack,fst . runWriter $ physicsTick pm st)
@@ -461,26 +463,77 @@
     , "and you must discover the secret flaws in the locks designed by your colleagues."
     , ""
     , "You may put forward up to three prototype locks. They will guard the secrets you discover."
-    , "If you pick a colleague's lock, the rules require that you have a note written on your solution."
-    , "A note proves that a solution was found, while revealing no more of its details than necessary."
-    , "Composing notes is a tricky and ritual-bound art of its own, performed by independent experts."
-    , "To declare your success, you must secure your note behind a lock of your own."
+    , "If you pick a colleague's lock, the rules require that a note is written on your solution."
+    , "A note proves that a solution was found, while revealing no more details than necessary."
+    --, "Composing notes is a tricky and ritual-bound art of its own, performed by independent experts."
+    , "To declare your solution, you must secure your note behind a lock of your own."
     , "If you are able to unlock a lock, you automatically read all the notes it secures."
     , "If you read three notes on a lock, you will piece together the secrets of unlocking that lock."
     , ""
     , "The game judges players relative to each of their peers. There are no absolute rankings."
     , "You win a point of esteem against another player for each of their locks for which either:"
-    , "you have declared a note on the lock which the lock's owner has not read,"
-    , "or you have read three notes on the lock."
-    , "Relative esteem ranges from +3 (best) to -3 (worst), and is calculated as"
-    , "the number of their locks which win you a point minus the number of your locks which win them one."
+    , "you have solved the lock and declared a note which the lock's owner has not read, or"
+    , "you have read three notes on the lock."
+    , "You also win a point for each empty lock slot if you can unlock all full slots."
+    , "Relative esteem is the points you win minus the points they win; +3 is best, -3 is worst."
+    , ""
     , "If the secrets to one of your locks become widely disseminated, you may wish to replace it."
     , "However: once replaced, a lock is \"retired\", and the notes it secured are read by everyone."
     ]
 
+initiationHelpText :: Int -> [String]
+initiationHelpText 1 =
+    [ ""
+    , "So."
+    , ""
+    , "It seems your levels of manual and mental dexterity are adequate for picking locks."
+    , "Whether you also possess the deviousness required for their design, remains to be seen."
+    , ""
+    , "Nonetheless, we welcome you to our number. As for what exactly it is that you are joining..."
+    , "perhaps you think you have worked it all out already, but let me explain."
+    , ""
+    , "But first: for reasons that will become clear,"
+    , "our members are known exclusively by pseudonyms - by tradition, a triplet of letters or symbols."
+    , "I am eager to hear what codename you will choose for yourself; do be thinking about it."
+    ]
+initiationHelpText 2 =
+    [ ""
+    , "Now."
+    , ""
+    , "As you fatefully determined, every lock permitted in the city has a fatal hidden flaw."
+    , "Those whose duties require it are entrusted with the secrets required to pick these locks."
+    , "Those who unauthorisedly discover said secrets... come to us."
+    , ""
+    , "Our task is to produce the superficially secure locks necessary for this system:"
+    , "locks pickable with minimal tools, but with this fact obscured by their mechanical complexity."
+    , ""
+    , "To push the our designs to ever new heights of intricacy, we run a ritual game."
+    , "You are to be its newest player."
+    ]
+initiationHelpText 3 =
+    [ ""
+    , "Each player designs locks, and each player attempts to solve the locks designed by the others."
+    , ""
+    , "You may put forward up to three prototype locks."
+    , "They will guard the secrets you discover: when you pick a colleague's lock,"
+    , "you may declare the fact by placing notes on its solution behind one of your locks."
+    , "As long as the owner of the lock you picked is unable to read your notes,"
+    , "you score a point against them."
+    , ""
+    , "If you find a lock too difficult or trivial for you to pick yourself,"
+    , "you may find that reading other players' notes on it will lead you to a solution."
+    , ""
+    , "The finer details of the rules can wait."
+    , "Go now; choose a codename, explore the locks we have set,"
+    , "and begin your own experiments in the ever-rewarding art of lock design."
+    ]
+initiationHelpText _ = []
+
+
 firstEditHelpText :: [String]
 firstEditHelpText =
     [ "Design a lock to protect your secrets."
+    , ""
     , "It must be possible to pick your lock by pulling a sprung bolt from the hole in the top-right,"
     , "but you should place blocks, springs, pivots, and balls to make this as difficult as possible."
     , ""
diff --git a/NEWS b/NEWS
--- a/NEWS
+++ b/NEWS
@@ -1,7 +1,12 @@
 This is an abbreviated summary; see the git log for gory details.
 
+0.7.1:
+    Rework tutorial and intro.
+    Animate movement.
+    Prevent dragging wrench off-path from generating a move.
+    Various UI improvements.
 0.7.0.1:
-    Fix compile error on older ghc versions
+    Fix compile error on older ghc versions.
 0.7:
     Encrypt passwords when communicating with server
         (in previous versions, they were salted and hashed but not encrypted).
diff --git a/SDLGlyph.hs b/SDLGlyph.hs
--- a/SDLGlyph.hs
+++ b/SDLGlyph.hs
@@ -38,6 +38,9 @@
 
 data Glyph
     = TileGlyph Tile Pixel
+    | SpringGlyph HexDir HexDir SpringExtension HexDir Pixel
+    | PivotGlyph TorqueDir HexDir Pixel
+    | ArmGlyph TorqueDir HexDir Pixel
     | BlockedArm HexDir TorqueDir Pixel
     | TurnedArm HexDir TorqueDir Pixel
     | BlockedBlock Tile HexDir Pixel
@@ -53,6 +56,7 @@
     | UseSoundsButton Bool
     | WhsButtonsButton (Maybe WrHoSel)
     | FullscreenButton Bool
+    | DisplacedGlyph HexDir Glyph
     | UnfreshGlyph
     deriving (Eq, Ord, Show)
 
@@ -137,11 +141,13 @@
 	    -- Maybe we could truncate the spring glyphs to a hex?
 	    TileGlyph (BlockTile adjs) _ -> null adjs
 	    TileGlyph (SpringTile extn dir) _ -> False
+            SpringGlyph _ _ _ _ _ -> False
 	    FilledHexGlyph _ -> False
 	    HollowGlyph _ -> False
 	    BlockedBlock _ _ _ -> False
 	    BlockedPush _ _ -> False
 	    CollisionMarker -> False
+            DisplacedGlyph _ _ -> False
 	    _ -> True
 
 renderGlyph :: Glyph -> RenderM ()
@@ -159,38 +165,13 @@
 		]
 
 renderGlyph (TileGlyph (SpringTile extn dir) col) =
-    thickLinesR points 1 $ brightness col
-	where
-            n :: Int
-	    n = 3*case extn of
-		Stretched -> 1
-		Relaxed -> 2
-		Compressed -> 4
-	    brightness = if extn == Relaxed then dim else bright
-	    dir' = if dir == zero then hu else dir
-	    s = corner (hextant dir' - 1)
-	    off = corner (hextant dir')
-	    e = corner (hextant dir' - 3)
-	    points = [ b +^ (fi i / fi n) **^ (e -^ s)
-		| i <- [0..n]
-		, i`mod`3 /= 1
-		, let b = if i`mod`3==0 then s else off ]
+    renderGlyph $ SpringGlyph zero zero extn dir col
 
 renderGlyph (TileGlyph (PivotTile dir) col) = do
-    rimmedCircleR zero (7/8) col $ bright col
-    when (dir /= zero)
-	$ aaLineR from to $ bright col
-    return ()
-	where
-	    from = (7/8) **^ edge (neg dir)
-	    to = (7/8) **^ edge dir
+    renderGlyph $ PivotGlyph 0 dir col
 
 renderGlyph (TileGlyph (ArmTile dir _) col) =
-    thickLineR from to 1 col
-	where
-	    dir' = if dir == zero then hu else dir
-	    from = edge $ neg dir'
-	    to = innerCorner dir'
+    renderGlyph $ ArmGlyph 0 dir col
 
 renderGlyph (TileGlyph HookTile col) =
     rimmedCircleR zero (7/8) col $ bright col
@@ -213,6 +194,44 @@
 renderGlyph (TileGlyph BallTile col) =
     rimmedCircleR zero (7/8) (faint col) (obscure col)
 
+renderGlyph (SpringGlyph rootDisp endDisp extn dir col) =
+    thickLinesR points 1 $ brightness col
+	where
+            n :: Int
+	    n = 3*case extn of
+		Stretched -> 1
+		Relaxed -> 2
+		Compressed -> 4
+	    brightness = if extn == Relaxed then dim else bright
+	    dir' = if dir == zero then hu else dir
+	    s = corner (hextant dir' - 1) +^ innerCorner endDisp
+	    off = corner (hextant dir') +^ innerCorner endDisp
+	    e = corner (hextant dir' - 3) +^ innerCorner rootDisp
+	    points = [ b +^ (fi i / fi n) **^ (e -^ s)
+		| i <- [0..n]
+		, i`mod`3 /= 1
+		, let b = if i`mod`3==0 then s else off ]
+
+renderGlyph (PivotGlyph rot dir col) = do
+    rimmedCircleR zero (7/8) col $ bright col
+    when (dir /= zero)
+	$ aaLineR from to $ bright col
+    return ()
+	where
+	    from = rotFVec th c $ (7/8) **^ edge (neg dir)
+	    to = rotFVec th c $ (7/8) **^ edge dir
+            c = FVec 0 0
+            th = - fi rot * pi / 12
+
+renderGlyph (ArmGlyph rot dir col) =
+    thickLineR from to 1 col
+	where
+	    dir' = if dir == zero then hu else dir
+	    from = rotFVec th c $ edge $ neg dir'
+	    to = rotFVec th c $ innerCorner dir'
+            c = (2 **^ edge (neg dir'))
+            th = - fi rot * pi / 12
+
 renderGlyph (BlockedArm armdir tdir col) =
     aaLineR from to col
 	where
@@ -315,6 +334,9 @@
 	    | dir <- hexDirs ]
 	corners' = map (((2/3)**^) . corner) [0..5]
 
+renderGlyph (DisplacedGlyph dir glyph) =
+    displaceRender (innerCorner dir) $ renderGlyph glyph
+
 renderGlyph (UnfreshGlyph) = do
     let col = bright red
     renderGlyph (HollowInnerGlyph col)
@@ -338,7 +360,7 @@
     where
 	colAt v@(HexVec hx hy hz) = let
 		[r,g,b] = map (\h -> fi $ ((0xff*)$ 5 + abs h)`div`maxR) [hx,hy,hz]
-		a = fi $ (0x90 * (maxR - abs (hexLen v)))`div`maxR
+		a = fi $ (0x70 * (maxR - abs (hexLen v)))`div`maxR
 	    in rgbaToPixel (r,g,b,a)
 
 drawBlocked :: GameState -> PieceColouring -> Bool -> Force -> RenderM ()
diff --git a/SDLRender.hs b/SDLRender.hs
--- a/SDLRender.hs
+++ b/SDLRender.hs
@@ -230,6 +230,7 @@
 	    | dir == hu = [2/3, 0]
 	    | dir == hv = [-1/3, -ylen]
 	    | dir == hw = [-1/3, ylen]
+            | dir == zero = [0,0]
 	    | not (isHexDir dir) = error "innerCorner: not a hexdir"
 	    | otherwise = map (\z -> -z) $ f $ neg dir
 
@@ -244,6 +245,15 @@
 	    | not (isHexDir dir) = error "edge: not a hexdir"
 	    | otherwise = map (\z -> -z) $ f $ neg dir
 
+rotFVec :: Float -> FVec -> FVec -> FVec
+rotFVec th (FVec cx cy) v@(FVec x y)
+    | th == 0 = v
+    | otherwise = FVec (cx + c*dx-s*dy) (cy + s*dx+c*dy)
+    where
+        dx = x-cx
+        dy = y-cy
+        c = cos th
+        s = sin th
 
 black = Pixel 0x01000000
 white = Pixel 0xffffff00
diff --git a/SDLUI.hs b/SDLUI.hs
--- a/SDLUI.hs
+++ b/SDLUI.hs
@@ -87,7 +87,7 @@
 type UIM = StateT UIState IO
 nullUIState = UIState 0 0 Nothing Nothing emptyCachedGlyphs Nothing Map.empty Map.empty []
     defaultUIOptions Nothing Map.empty Nothing Nothing 0 0 Nothing Nothing Nothing
-    (zero,False) Nothing Nothing False (PHS zero) Map.empty 0 Nothing 25
+    (zero,False) Nothing Nothing False (PHS zero) Map.empty 0 Nothing 50
 #ifdef SOUND
     Map.empty
 #endif
@@ -101,9 +101,10 @@
     , showButtonText::Bool
     , useSounds::Bool
     , uiAnimTime::Word32
+    , shortUiAnimTime::Word32
     }
     deriving (Eq, Ord, Show, Read)
-defaultUIOptions = UIOptions False ShowBlocksBlocking Nothing True False True True 100
+defaultUIOptions = UIOptions False ShowBlocksBlocking Nothing True False True True 100 20
 
 modifyUIOptions :: (UIOptions -> UIOptions) -> UIM ()
 modifyUIOptions f = modify $ \s -> s { uiOptions = f $ uiOptions s }
@@ -161,7 +162,6 @@
 	IMPlay ->
 	    [ markGroup ]
 	    ++ whsBGs mwhs mode
-	    ++ [ singleButton tr CmdOpen 1 [("open", hu+^neg hw)] ]
 	IMReplay -> [ markGroup ]
 	IMMeta ->
 	    [ singleButton serverPos CmdSetServer 0 [("server",7*^neg hu)]
@@ -566,23 +566,24 @@
 	    -- to the right states:
 	    let (globalAlerts,transitoryAlerts) = partition isGlobalAlert alerts
 		splitAlerts frameAs (AlertIntermediateState st' : as) =
-		    (frameAs,st') : splitAlerts [] as
+		    (frameAs,st',True) : splitAlerts [] as
 		splitAlerts frameAs (a:as) =
 		    splitAlerts (a:frameAs) as
-		splitAlerts frameAs [] = [(frameAs,st)]
+		splitAlerts frameAs [] = [(frameAs,st,False)]
 		isGlobalAlert (AlertAppliedForce _) = False
 		isGlobalAlert (AlertIntermediateState _) = False
 		isGlobalAlert _ = True
 	    let animAlertedStates = nub $
 		    let ass = splitAlerts [] transitoryAlerts
-		    in if last ass == ([],st) then ass else ass ++ [([],st)]
+		    in if last ass == ([],st,False) then ass else ass ++ [([],st,False)]
 	    let frames = length animAlertedStates
-	    let (drawAlerts',drawSt) = animAlertedStates !! animFrameToDraw
+	    let (drawAlerts',drawSt,isIntermediate) = animAlertedStates !! animFrameToDraw
 	    let drawAlerts = drawAlerts' ++ globalAlerts
 	    -- let drawAlerts = takeWhile (/= AlertIntermediateState drawSt) alerts
 	    nextIsSet <- isJust <$> gets nextAnimFrameAt
 	    when (not nextIsSet && frames > animFrameToDraw+1) $ do
-		time <- uiAnimTime <$> gets uiOptions
+		time <- (if isIntermediate then uiAnimTime else shortUiAnimTime) <$>
+                    gets uiOptions
 		modify $ \ds -> ds { nextAnimFrameAt = Just $ now + time }
 
 	    let board = stateBoard drawSt
@@ -594,9 +595,54 @@
 	    modify $ \ds -> ds { dispLastCol = colouring }
 	    gsSurf <- liftM fromJust $ gets gsSurface
 	    renderToMainWithSurf gsSurf $ do
+                let tileGlyphs = fmap (ownedTileGlyph colouring highlight) board
+
+                    applyAlert (AlertAppliedForce f@(Torque idx tdir)) =
+                        let poss = case getpp drawSt idx of
+                                PlacedPiece pos (Pivot arms) -> pos : map (+^pos) arms
+                                PlacedPiece pos (Hook arm _) -> [arm+^pos]
+                                _ -> []
+                            rotateGlyph (TileGlyph (ArmTile dir _) col) =
+                                ArmGlyph (-tdir) dir col
+                            rotateGlyph (TileGlyph (PivotTile dir) col) =
+                                PivotGlyph (-tdir) dir col
+                        in flip (foldr . Map.adjust $ rotateGlyph) poss
+                    applyAlert (AlertAppliedForce f@(Push idx dir)) =
+                        displaceFootprint . displaceSprings
+                        where
+                            displace = DisplacedGlyph $ neg dir
+                            displaceSpringGlyph isRoot (TileGlyph (SpringTile extn sdir) col) =
+                                displaceSpringGlyph isRoot $ SpringGlyph zero zero extn sdir col
+                            displaceSpringGlyph isRoot (SpringGlyph rdisp edisp extn sdir col)
+                                | isRoot = SpringGlyph (neg dir) edisp extn sdir col
+                                | otherwise = SpringGlyph rdisp (neg dir) extn sdir col
+                            displaceSpringGlyph _ glyph = glyph
+                            displaceFootprint =
+                                flip (foldr . Map.adjust $ displace) $
+                                    plPieceFootprint $ getpp drawSt idx
+
+                            displaceSpring isRoot c@(Connection root end (Spring sdir _))
+                                | dir `elem` [sdir,neg sdir] =
+                                    Map.adjust (displaceSpringGlyph isRoot) $
+                                        if isRoot
+                                            then sdir +^ locusPos drawSt root
+                                            else neg sdir +^ locusPos drawSt end
+                                | isRoot =
+                                    flip (foldr . Map.adjust $ displace) $
+                                        connectionFootPrint drawSt c
+                                | otherwise = id
+                            displaceSpring _ _ = id
+
+                            displaceSprings =
+                                (flip (foldr $ displaceSpring True) $ springsRootAtIdx drawSt idx) .
+                                (flip (foldr $ displaceSpring False) $ springsEndAtIdx drawSt idx)
+                    applyAlert _ = id
+
+                    applyAlerts = flip (foldr applyAlert) drawAlerts
+
 		erase
 		sequence_ [ drawAt glyph pos |
-		    (pos,glyph) <- Map.toList $ fmap (ownedTileGlyph colouring highlight) board
+		    (pos,glyph) <- Map.toList $ applyAlerts tileGlyphs
 		    ]
 
 		when (showBlocks uiopts /= ShowBlocksNone) $ sequence_
@@ -609,8 +655,8 @@
 		    -- AlertResistedForce force <- drawAlerts ]
 		    ++ [ drawAt CollisionMarker pos
 			| AlertCollision pos <- drawAlerts ]
-		    ++ [ drawApplied drawSt colouring force
-			| AlertAppliedForce force <- drawAlerts ]
+		    -- ++ [ drawApplied drawSt colouring force
+		    --    | AlertAppliedForce force <- drawAlerts ]
 	    vidSurf <- liftIO getVideoSurface
 	    liftIO $ blitSurface gsSurf Nothing vidSurf Nothing
 
diff --git a/SDLUIMInstance.hs b/SDLUIMInstance.hs
--- a/SDLUIMInstance.hs
+++ b/SDLUIMInstance.hs
@@ -70,7 +70,8 @@
 	    drawShortMouseHelp mode
 	    refresh
 	where
-	drawMainState' (PlayState { psCurrentState=st, psLastAlerts=alerts, wrenchSelected=wsel, psIsTut=isTut }) = do
+        drawMainState' (PlayState { psCurrentState=st, psLastAlerts=alerts,
+                wrenchSelected=wsel, psIsTut=isTut, psSolved=solved }) = do
 	    canUndo <- null <$> gets psGameStateMoveStack
 	    canRedo <- null <$> gets psUndoneStack
 	    lift $ do
@@ -90,6 +91,10 @@
 			, isTool p
 			]
 		registerUndoButtons canUndo canRedo
+                registerButton (periphery 0) CmdOpen (if solved then 2 else 0) $
+                    [("open", hu+^neg hw)] ++ if solved && isTut
+                        then [("Click-->",9*^neg hu)]
+                        else []
 	drawMainState' (ReplayState { rsCurrentState=st, rsLastAlerts=alerts } ) = do
 	    canUndo <- null <$> gets rsGameStateMoveStack
 	    canRedo <- null <$> gets rsMoveStack
@@ -597,12 +602,23 @@
     showHelp IMMeta HelpPageGame = do
         renderToMain $ drawBasicHelpPage ("INTRICACY",red) (metagameHelpText,purple)
         return True
+    showHelp IMMeta (HelpPageInitiated n) = do
+        renderToMain $ drawBasicHelpPage ("Initiation complete",purple) (initiationHelpText n,red)
+        return True
     showHelp IMEdit HelpPageFirstEdit = do
         renderToMain $ drawBasicHelpPage ("Your first lock:",purple) (firstEditHelpText,green)
         return True
     showHelp _ _ = return False
 
     onNewMode mode = modify (\ds -> ds{needHoverUpdate=True}) >> say ""
+
+    withNoBG m = do
+        bg <- gets bgSurface
+        modify $ \uiState -> uiState{bgSurface=Nothing}
+        m
+        isNothing <$> gets bgSurface >>?
+            modify (\uiState -> uiState{bgSurface=bg})
+
 
 drawShortMouseHelp mode = do
     mwhs <- gets $ whsButtons.uiOptions
diff --git a/Server.hs b/Server.hs
--- a/Server.hs
+++ b/Server.hs
@@ -177,7 +177,7 @@
 
 showRequest :: ClientRequest -> String
 showRequest (ClientRequest ver mauth act) = show ver ++ " "
-    ++ maybe "" (\(Auth name pw) -> "Auth:" ++ name ++ ":" ++ take 4 pw) mauth ++ " "
+    ++ maybe "" (\(Auth name _) -> "Auth:" ++ name) mauth ++ " "
     ++ showAction act
 showAction :: Action -> String
 showAction (SetLock lock idx soln) = "SetLock " ++ show idx ++ " lock:"
diff --git a/Version.hs b/Version.hs
--- a/Version.hs
+++ b/Version.hs
@@ -11,4 +11,4 @@
 module Version where
 
 version :: String
-version = "0.7.0.1"
+version = "0.7.1"
diff --git a/intricacy.cabal b/intricacy.cabal
--- a/intricacy.cabal
+++ b/intricacy.cabal
@@ -1,5 +1,5 @@
 name:                intricacy
-version:             0.7.0.1
+version:             0.7.1
 synopsis:            A game of competitive puzzle-design
 homepage:            http://mbays.freeshell.org/intricacy
 license:             GPL-3
@@ -9,7 +9,7 @@
 -- copyright:           
 category:            Game
 build-type:          Simple
-cabal-version:       >=1.8
+cabal-version:       >=1.18
 data-files: VeraMoBd.ttf tutorial/*.lock tutorial/*.text sounds/*.ogg
 extra-doc-files: README BUILD NEWS tutorial-extra/*.lock tutorial-extra/README
 extra-source-files: Main_stub.h
@@ -101,7 +101,7 @@
 
   ghc-options: -fno-warn-tabs
   other-modules: AsciiLock, BinaryInstances, BoardColouring, Cache, Command,
-      CursesRender, CursesUI, CursesUIMInstance, CVec, Database, Debug,
+      CursesRender, CursesUI, CursesUIMInstance, CVec, Database,
       EditGameState, Frame, GameState, GameStateTypes, GraphColouring, Hex, Init,
       InputMode, Interact, InteractUtil, KeyBindings, Lock, MainState,
       Maxlocksize, Metagame, Mundanities, Physics, Protocol, SDLGlyph,
@@ -126,5 +126,5 @@
   main-is:  Server.hs
   ghc-options: -fno-warn-tabs
   other-modules: AsciiLock, BinaryInstances, BoardColouring, CVec, Database,
-      Debug, Frame, GameState, GameStateTypes, GraphColouring, Hex, Lock,
+      Frame, GameState, GameStateTypes, GraphColouring, Hex, Lock,
       Maxlocksize, Metagame, Mundanities, Physics, Protocol, Util, Version
diff --git a/tutorial-extra/1-springChains.lock b/tutorial-extra/1-springChains.lock
new file mode 100644
--- /dev/null
+++ b/tutorial-extra/1-springChains.lock
@@ -0,0 +1,17 @@
+        % % % % % % % % %          
+       %                 % % % %   
+      %                   %     %  
+     %                     %     % 
+    % S S S S " S S S S & & & & & %
+   %   o -   "   # # #       %   % 
+  %   /         #     %       % %  
+ %             #     %       [ %   
+%           #       %       [   %  
+ %           " S S %       [   %   
+  % S S S S " "   %       #   %    
+ % %           % % S S S S " %     
+% * '                       %      
+ % @ %                     %       
+  % % %                   %        
+       %                 %         
+        % % % % % % % % %          
diff --git a/tutorial-extra/2-snookered.lock b/tutorial-extra/2-snookered.lock
new file mode 100644
--- /dev/null
+++ b/tutorial-extra/2-snookered.lock
@@ -0,0 +1,17 @@
+        " " " " " " " " "          
+       " S S S # S S S % " " " "   
+      "       #         % "     "  
+     "       #     &     % "     " 
+    "       # O O   &     % % % % "
+   "       # O O   & "     % "   " 
+  "       #       & " " "     " "  
+ "   & & & & & & & % C C # #   "   
+" & &       & & & . o         % "  
+ " O O O O O & & o - '       % "   
+  " O O O O   &   . o         "    
+ " " O O O   & & O % \       "     
+" * ' O O   & &   %   o     "      
+ " @ " O   &     # Z #     "       
+  " " "         # # Z     "        
+       "   # # # # # Z   "         
+        " " " " " " " " "          
diff --git a/tutorial-extra/3-plug.lock b/tutorial-extra/3-plug.lock
new file mode 100644
--- /dev/null
+++ b/tutorial-extra/3-plug.lock
@@ -0,0 +1,17 @@
+        " " " " " " " " "          
+       " #             ( " " " "   
+      "   #           (   "     "  
+     "   # #         (     " #   " 
+    "   #   #       (       #     "
+   "         #     (       # "   " 
+  "   & &   %     (         # " "  
+ "   & & & & Z   (         # # "   
+"   # C C & & Z (       # #   # "  
+ "   & & & & & #             7 "   
+  "   & & & & & ]           7 "    
+ " "     & & &   ] %       7 "     
+" * '   o \ O   # " %     7 "      
+ " @ "   ` o     #   %   7 "       
+  " " "   #   %   #   % 7 "        
+       "     % %       % "         
+        " " " " " " " " "          
diff --git a/tutorial-extra/4-plunger.lock b/tutorial-extra/4-plunger.lock
new file mode 100644
--- /dev/null
+++ b/tutorial-extra/4-plunger.lock
@@ -0,0 +1,17 @@
+        " " " " " " " " "          
+       "     #       # # " " " "   
+      "   #   #     # #   "     "  
+     "     #   ]   # #     " %   " 
+    "   #   # % % # # & & & %     "
+   "     #   ]   % #   7   % "   " 
+  "   #   # " " # '   7   % % " "  
+ "     #   ]   " o % %   '   % "   
+"       # % % # '   7 . o     % "  
+ "   #   ]   % o   7         1 "   
+  "   # " " # ' # #         1 "    
+ " "   #   " o   7         # "     
+" * '     % '   7           "      
+ " @ "   % o % %           "       
+  " " " %     7           "        
+       "     7           "         
+        " " " " " " " " "          
diff --git a/tutorial-extra/5-cogs.lock b/tutorial-extra/5-cogs.lock
new file mode 100644
--- /dev/null
+++ b/tutorial-extra/5-cogs.lock
@@ -0,0 +1,17 @@
+        " " " " " " " " "          
+       " # # # # [ # # # " " " "   
+      " # # # # [ # S S % "     "  
+     " # # # # [       % % "     " 
+    " # # # # [           % % %   "
+   " #   \   % % % %     %   "   " 
+  "       o - ' % [     . o   " "  
+ "   # # / . o   [ # #     `   "   
+"   # # # # \ ` # # [     . o   "  
+ "   # #   ' o - ' [         ` "   
+  "     . o / . o %       #   "    
+ " "       ` #   ` %     #   "     
+" * '     # # # % % % % #   "      
+ " @ "   # # #   % # # #   "       
+  " " "   # # % % # # #   "        
+       "                 "         
+        " " " " " " " " "          
diff --git a/tutorial-extra/5-springChains.lock b/tutorial-extra/5-springChains.lock
deleted file mode 100644
--- a/tutorial-extra/5-springChains.lock
+++ /dev/null
@@ -1,17 +0,0 @@
-        % % % % % % % % %          
-       %                 % % % %   
-      %                   %     %  
-     %                     %     % 
-    % S S S S " S S S S & & & & & %
-   %   o -   "   # # #       %   % 
-  %   /         #     %       % %  
- %             #     %       [ %   
-%           #       %       [   %  
- %           " S S %       [   %   
-  % S S S S " "   %       #   %    
- % %           % % S S S S " %     
-% * '                       %      
- % @ %                     %       
-  % % %                   %        
-       %                 %         
-        % % % % % % % % %          
diff --git a/tutorial-extra/6-snookered.lock b/tutorial-extra/6-snookered.lock
deleted file mode 100644
--- a/tutorial-extra/6-snookered.lock
+++ /dev/null
@@ -1,17 +0,0 @@
-        " " " " " " " " "          
-       " S S S # S S S % " " " "   
-      "       #         % "     "  
-     "       #     &     % "     " 
-    "       # O O   &     % % % % "
-   "       # O O   & "     % "   " 
-  "       #       & " " "     " "  
- "   & & & & & & & % C C # #   "   
-" & &       & & & . o         % "  
- " O O O O O & & o - '       % "   
-  " O O O O   &   . o         "    
- " " O O O   & & O % \       "     
-" * ' O O   & &   %   o     "      
- " @ " O   &     # Z #     "       
-  " " "         # # Z     "        
-       "   # # # # # Z   "         
-        " " " " " " " " "          
diff --git a/tutorial-extra/7-plunger.lock b/tutorial-extra/7-plunger.lock
deleted file mode 100644
--- a/tutorial-extra/7-plunger.lock
+++ /dev/null
@@ -1,17 +0,0 @@
-        " " " " " " " " "          
-       "     #       # # " " " "   
-      "   #   #     # #   "     "  
-     "     #   ]   # #     " %   " 
-    "   #   # % % # # & & & %     "
-   "     #   ]   % #   7   % "   " 
-  "   #   # " " # '   7   % % " "  
- "     #   ]   " o % %   '   % "   
-"       # % % # '   7 . o     % "  
- "   #   ]   % o   7         1 "   
-  "   # " " # ' # #         1 "    
- " "   #   " o   7         # "     
-" * '     % '   7           "      
- " @ "   % o % %           "       
-  " " " %     7           "        
-       "     7           "         
-        " " " " " " " " "          
diff --git a/tutorial-extra/8-cogs.lock b/tutorial-extra/8-cogs.lock
deleted file mode 100644
--- a/tutorial-extra/8-cogs.lock
+++ /dev/null
@@ -1,17 +0,0 @@
-        " " " " " " " " "          
-       " # # # # [ # # # " " " "   
-      " # # # # [ # S S % "     "  
-     " # # # # [       % % "     " 
-    " # # # # [           % % %   "
-   " #   \   % % % %     %   "   " 
-  "       o - ' % [     . o   " "  
- "   # # / . o   [ # #     `   "   
-"   # # # # \ ` # # [     . o   "  
- "   # #   ' o - ' [         ` "   
-  "     . o / . o %       #   "    
- " "       ` #   ` %     #   "     
-" * '     # # # % % % % #   "      
- " @ "   # # #   % # # #   "       
-  " " "   # # % % # # #   "        
-       "                 "         
-        " " " " " " " " "          
diff --git a/tutorial/01-winning.lock b/tutorial/01-winning.lock
new file mode 100644
--- /dev/null
+++ b/tutorial/01-winning.lock
@@ -0,0 +1,11 @@
+       " " " " " "       
+      "           " " "  
+     "             "   " 
+    " S S S S S S # # # "
+   "             #   " " 
+  "                   "  
+ " "                 "   
+" * '               "    
+ " @ "             "     
+  " " "           "      
+       " " " " " "       
diff --git a/tutorial/01-winning.text b/tutorial/01-winning.text
new file mode 100644
--- /dev/null
+++ b/tutorial/01-winning.text
@@ -0,0 +1,1 @@
+drag your tools to pull the bolt aside, then press 'O'
diff --git a/tutorial/02-tools.lock b/tutorial/02-tools.lock
new file mode 100644
--- /dev/null
+++ b/tutorial/02-tools.lock
@@ -0,0 +1,11 @@
+       " " " " " "       
+      " # # # # # " " "  
+     " # # # # # # "   " 
+    " S S S S S S % % % "
+   " # #         %   " " 
+  " #     # # # # #   "  
+ " "   # # # # # #   "   
+" * '   # #         "    
+ " @ "   #   # # # "     
+  " " "     # # # "      
+       " " " " " "       
diff --git a/tutorial/02-tools.text b/tutorial/02-tools.text
new file mode 100644
--- /dev/null
+++ b/tutorial/02-tools.text
@@ -0,0 +1,1 @@
+the wrench can fit where the hook gets stuck
diff --git a/tutorial/03-wrench.lock b/tutorial/03-wrench.lock
new file mode 100644
--- /dev/null
+++ b/tutorial/03-wrench.lock
@@ -0,0 +1,13 @@
+       " " " " " " "       
+      "     #       " " "  
+     " S S # #   %   "   " 
+    "             % % % % "
+   "   o   # S S S S % " " 
+  "     `             # "  
+ "   % % % % % %   # #   " 
+  " %   %               "  
+ " "   % % %     #     "   
+" * '     % %         "    
+ " @ " %   % o       "     
+  " " " %   /     # "      
+       " " " " " " "       
diff --git a/tutorial/03-wrench.text b/tutorial/03-wrench.text
new file mode 100644
--- /dev/null
+++ b/tutorial/03-wrench.text
@@ -0,0 +1,1 @@
+the wrench moves in a straight line until blocked
diff --git a/tutorial/04-hook.lock b/tutorial/04-hook.lock
new file mode 100644
--- /dev/null
+++ b/tutorial/04-hook.lock
@@ -0,0 +1,13 @@
+       " " " " " " "       
+      " %   # #   % " " "  
+     " %     Z #   % " # " 
+    " % %   % Z     % #   "
+   " %   # % % Z     # " " 
+  " % %   Z % % Z     # "  
+ " % % %   Z % % %     # " 
+  " % % %   % % %   % # "  
+ " "   % ]     %   # # "   
+" * '     ]   % %   # "    
+ " @ "   # #   % % 7 "     
+  " " "   % % % % 7 "      
+       " " " " " " "       
diff --git a/tutorial/04-hook.text b/tutorial/04-hook.text
new file mode 100644
--- /dev/null
+++ b/tutorial/04-hook.text
@@ -0,0 +1,1 @@
+turn hook with mouse wheel to pull springs aside; numpad and vi keys also supported
diff --git a/tutorial/05-blockWrench.lock b/tutorial/05-blockWrench.lock
new file mode 100644
--- /dev/null
+++ b/tutorial/05-blockWrench.lock
@@ -0,0 +1,11 @@
+       " " " " " "       
+      " #         " " "  
+     " #   #   %   "   " 
+    " #   # S S % % % % "
+   "                 " " 
+  "   # #   #   # #   "  
+ " "   #   # #   #   "   
+" * '               "    
+ " @ "   # # # #   "     
+  " " "           "      
+       " " " " " "       
diff --git a/tutorial/05-blockWrench.text b/tutorial/05-blockWrench.text
new file mode 100644
--- /dev/null
+++ b/tutorial/05-blockWrench.text
@@ -0,0 +1,1 @@
+the hook can be used to guide the wrench
diff --git a/tutorial/06-balls.lock b/tutorial/06-balls.lock
new file mode 100644
--- /dev/null
+++ b/tutorial/06-balls.lock
@@ -0,0 +1,11 @@
+       " " " " " "       
+      " & & S S # " " "  
+     " & & &     # "   " 
+    " & & & &   # # # # "
+   "         &   #   " " 
+  "     #   & &   #   "  
+ " " O #     &       "   
+" * ' # #     O #   "    
+ " @ " #   O     # "     
+  " " " #       # "      
+       " " " " " "       
diff --git a/tutorial/06-balls.text b/tutorial/06-balls.text
new file mode 100644
--- /dev/null
+++ b/tutorial/06-balls.text
@@ -0,0 +1,1 @@
+balls can be pushed around to allow or block movement
diff --git a/tutorial/07-pivots.lock b/tutorial/07-pivots.lock
new file mode 100644
--- /dev/null
+++ b/tutorial/07-pivots.lock
@@ -0,0 +1,11 @@
+       " " " " " "       
+      " \     # # " " "  
+     "   o -   \ ' " # " 
+    " % /   "   o - #   "
+   " % %   " "   # # " " 
+  " % % %   " O   # # "  
+ " "         . o - 7 "   
+" * ' . o - &   ` 7 "    
+ " @ " O     & & & "     
+  " " " # #   & & "      
+       " " " " " "       
diff --git a/tutorial/07-pivots.text b/tutorial/07-pivots.text
new file mode 100644
--- /dev/null
+++ b/tutorial/07-pivots.text
@@ -0,0 +1,1 @@
+push arms to rotate them around their pivots
diff --git a/tutorial/08-springs.lock b/tutorial/08-springs.lock
new file mode 100644
--- /dev/null
+++ b/tutorial/08-springs.lock
@@ -0,0 +1,17 @@
+        " " " " " " " " "          
+       " # # # # # # # # " " " "   
+      " # #               "     "  
+     " # #   % % % %   #   " # # " 
+    " # # #   % % %     # # #     "
+   "               %     # # "   " 
+  "   # #   # # #   %     # # " "  
+ "     ]   " S S #   %       # "   
+"       ]   " % % % % % %     # "  
+ "       ]   " ] % % % % %   7 "   
+  "       ] % " ] % % % %   7 "    
+ " "     % % % " " % % S S & "     
+" * '     % Z   o - % % % % "      
+ " @ "       Z / & C C C C "       
+  " " "       Z & & & &   "        
+       "       Z & & &   "         
+        " " " " " " " " "          
diff --git a/tutorial/08-springs.text b/tutorial/08-springs.text
new file mode 100644
--- /dev/null
+++ b/tutorial/08-springs.text
@@ -0,0 +1,1 @@
+A spring's length can double when stretched, and halve when compressed
diff --git a/tutorial/09-traps.lock b/tutorial/09-traps.lock
new file mode 100644
--- /dev/null
+++ b/tutorial/09-traps.lock
@@ -0,0 +1,13 @@
+       & & & & & & &       
+      &             & & &  
+     & % O % S S "   &   & 
+    &         #   " " " " &
+   & "         O   " " & & 
+  & " "   # #     # # # &  
+ & "                   # & 
+  & " " "         # # # &  
+ & &   " } # #   #     &   
+& * '     % # #   #   &    
+ & @ &     O       # &     
+  & & &     # # # # &      
+       & & & & & & &       
diff --git a/tutorial/09-traps.text b/tutorial/09-traps.text
new file mode 100644
--- /dev/null
+++ b/tutorial/09-traps.text
@@ -0,0 +1,1 @@
+if you make a mistake, you can undo moves ('X') or reset to the start ('^')
diff --git a/tutorial/1-winning.lock b/tutorial/1-winning.lock
deleted file mode 100644
--- a/tutorial/1-winning.lock
+++ /dev/null
@@ -1,11 +0,0 @@
-       " " " " " "       
-      "           " " "  
-     "             "   " 
-    " S S S S S S # # # "
-   "             #   " " 
-  "                   "  
- " "                 "   
-" * '               "    
- " @ "             "     
-  " " "           "      
-       " " " " " "       
diff --git a/tutorial/1-winning.text b/tutorial/1-winning.text
deleted file mode 100644
--- a/tutorial/1-winning.text
+++ /dev/null
@@ -1,1 +0,0 @@
-drag your tools to pull the bolt aside, then press 'O'
diff --git a/tutorial/10-puzzle.lock b/tutorial/10-puzzle.lock
new file mode 100644
--- /dev/null
+++ b/tutorial/10-puzzle.lock
@@ -0,0 +1,17 @@
+        " " " " " " " " "          
+       "                 " " " "   
+      "   # S S S S S %   "     "  
+     "     O   "       %   "     " 
+    " S S & & & o -   % % % % % % "
+   "   & &   #   #           "   " 
+  "         # #   # #   # # # " "  
+ "         #   #             # "   
+"               # # #   # # # # "  
+ "             # #             "   
+  "             #         O   "    
+ " "   #     %   # #   # # # "     
+" * '     % % %           # "      
+ " @ "   O % } % % % o   # "       
+  " " " % O % "     /   # "        
+       " %     O       # "         
+        " " " " " " " " "          
diff --git a/tutorial/10-puzzle.text b/tutorial/10-puzzle.text
new file mode 100644
--- /dev/null
+++ b/tutorial/10-puzzle.text
@@ -0,0 +1,1 @@
+your final test!
diff --git a/tutorial/2-tools.lock b/tutorial/2-tools.lock
deleted file mode 100644
--- a/tutorial/2-tools.lock
+++ /dev/null
@@ -1,13 +0,0 @@
-       " " " " " " "       
-      "     #       " " "  
-     " S S # #   %   "   " 
-    "             % % % % "
-   "   o   # S S S S % " " 
-  "     `             # "  
- "   % % % % % %   # #   " 
-  " %   %               "  
- " "   % % %     #     "   
-" * '     % %         "    
- " @ " %   % o       "     
-  " " " %   /     # "      
-       " " " " " " "       
diff --git a/tutorial/2-tools.text b/tutorial/2-tools.text
deleted file mode 100644
--- a/tutorial/2-tools.text
+++ /dev/null
@@ -1,1 +0,0 @@
-the wrench can fit where the hook can't, but keeps moving until it hits something
diff --git a/tutorial/3-puzzle.lock b/tutorial/3-puzzle.lock
deleted file mode 100644
--- a/tutorial/3-puzzle.lock
+++ /dev/null
@@ -1,17 +0,0 @@
-        & & & & & & & & &          
-       &                 & & & &   
-      &   # S S S S S "   &     &  
-     &     O   %       "   &     & 
-    & S S # # # o -   " " " " " " &
-   &   # #   %   #           &   & 
-  &         % %   # #   # # # & &  
- &         %   %             # &   
-&               % % %   # # # # &  
- &             % %             &   
-  &             %         O   &    
- & &         "   % %   % % % &     
-& * ' " " " " "     \     % &      
- & @ &         " " " o   % &       
-  & & &         "       % &        
-       &         "     % &         
-        & & & & & & & & &          
diff --git a/tutorial/3-puzzle.text b/tutorial/3-puzzle.text
deleted file mode 100644
--- a/tutorial/3-puzzle.text
+++ /dev/null
@@ -1,1 +0,0 @@
-if you make a mistake, you can undo moves ('X') or reset to the start ('^')
diff --git a/tutorial/4-hook.lock b/tutorial/4-hook.lock
deleted file mode 100644
--- a/tutorial/4-hook.lock
+++ /dev/null
@@ -1,13 +0,0 @@
-       & & & & & & &       
-      &     # #   % & & &  
-     &       Z #   % & # & 
-    &   " " " Z     % #   &
-   &   # C C " Z     # & & 
-  &   %   " " " Z     # &  
- &   7   " " " " "     # & 
-  & 7   " " " " "   % # &  
- & &   " " " " "   # # &   
-& * ' " " " " " "   # &    
- & @ & " " " " " " 7 &     
-  & & & " " " " " 7 &      
-       & & & & & & &       
diff --git a/tutorial/4-hook.text b/tutorial/4-hook.text
deleted file mode 100644
--- a/tutorial/4-hook.text
+++ /dev/null
@@ -1,1 +0,0 @@
-turn hook with mouse wheel to pull springs aside; numpad and vi keys also supported
diff --git a/tutorial/5-springs.lock b/tutorial/5-springs.lock
deleted file mode 100644
--- a/tutorial/5-springs.lock
+++ /dev/null
@@ -1,17 +0,0 @@
-        " " " " " " " " "          
-       " # # # # #   # # " " " "   
-      " # #       #       "     "  
-     " # #   % %       #   " # # " 
-    " # # #   % S S #   # # #     "
-   "                       # "   " 
-  "   & &   % % %         # # " "  
- "     ]   " S S %           # "   
-"       ]   " # # #     #     # "  
- "       ]   " ] # #   # #   7 "   
-  "       ] # " ] #   # #   7 "    
- " "     # # # " " # # S S % "     
-" * '     # Z   o - # # # # "      
- " @ "       Z / % C C C C "       
-  " " "       Z % % % %   "        
-       "       Z % % %   "         
-        " " " " " " " " "          
diff --git a/tutorial/5-springs.text b/tutorial/5-springs.text
deleted file mode 100644
--- a/tutorial/5-springs.text
+++ /dev/null
@@ -1,1 +0,0 @@
-A spring's length can double when stretched, and halve when compressed
diff --git a/tutorial/6-plug.lock b/tutorial/6-plug.lock
deleted file mode 100644
--- a/tutorial/6-plug.lock
+++ /dev/null
@@ -1,17 +0,0 @@
-        " " " " " " " " "          
-       " #             ( " " " "   
-      "   #           (   "     "  
-     "   # #         (     " #   " 
-    "   #   #       (       #     "
-   "         #     (       # "   " 
-  "   & &   %     (         # " "  
- "   & & & & Z   (         # # "   
-"   # C C & & Z (       # #   # "  
- "   & & & & & #             7 "   
-  "   & & & & & ]           7 "    
- " "     & & &   ] %       7 "     
-" * '   o \ O   # " %     7 "      
- " @ "   ` o     #   %   7 "       
-  " " "   #   %   #   % 7 "        
-       "     % %       % "         
-        " " " " " " " " "          
diff --git a/tutorial/6-plug.text b/tutorial/6-plug.text
deleted file mode 100644
--- a/tutorial/6-plug.text
+++ /dev/null
@@ -1,1 +0,0 @@
-compress the central springs with both tools at once
