diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,13 @@
 # Revision history for swarm
 
+## **0.3.0.1** - 2023-02-01
+
+A few critical bug fixes and improvements:
+
+* Make sure the base always has a `logger` installed in every tutorial level, by @byorgey in [#1067](https://github.com/swarm-game/swarm/pull/1067) and @xsebek in [#1073](https://github.com/swarm-game/swarm/pull/1073)
+* Allow dialog boxes to scroll by @byorgey in [#1071](https://github.com/swarm-game/swarm/pull/1071)
+* Fix bug that sometimes caused scenarios to be skipped or repeated by @byorgey in [#1065](https://github.com/swarm-game/swarm/pull/1065)
+
 ## **0.3.0.0** - 2023-01-30
 
 This is the first release of 2023! It contains:
diff --git a/data/scenarios/Tutorials/backstory.yaml b/data/scenarios/Tutorials/backstory.yaml
--- a/data/scenarios/Tutorials/backstory.yaml
+++ b/data/scenarios/Tutorials/backstory.yaml
@@ -62,6 +62,8 @@
     loc: [0,0]
     inventory:
       - [1, READY]
+    devices:
+      - logger
   - name: listener
     system: true
     display:
diff --git a/data/scenarios/Tutorials/bind2.yaml b/data/scenarios/Tutorials/bind2.yaml
--- a/data/scenarios/Tutorials/bind2.yaml
+++ b/data/scenarios/Tutorials/bind2.yaml
@@ -77,10 +77,12 @@
       attr: robot
     devices:
       - 3D printer
+      - logger
     inventory:
       - [1, solar panel]
       - [1, treads]
       - [1, grabber]
+      - [1, compass]
   - name: floorspot
     system: true
   - name: pedestal
diff --git a/data/scenarios/Tutorials/require.yaml b/data/scenarios/Tutorials/require.yaml
--- a/data/scenarios/Tutorials/require.yaml
+++ b/data/scenarios/Tutorials/require.yaml
@@ -56,6 +56,7 @@
       - [10, boat]
       - [10, grabber]
       - [10, scanner]
+      - [10, compass]
 known: [water]
 world:
   default: [blank]
diff --git a/src/Swarm/TUI/Controller.hs b/src/Swarm/TUI/Controller.hs
--- a/src/Swarm/TUI/Controller.hs
+++ b/src/Swarm/TUI/Controller.hs
@@ -189,6 +189,10 @@
   _ -> error "No tutorials exist!"
 
 -- | If we are in a New Game menu, advance the menu to the next item in order.
+--
+--   NOTE: be careful to maintain the invariant that the currently selected
+--   menu item is always the same as the currently played scenario!  `quitGame`
+--   is the only place this function should be called.
 advanceMenu :: Menu -> Menu
 advanceMenu = _NewGameMenu . ix 0 %~ BL.listMoveDown
 
@@ -401,7 +405,7 @@
       Just (Button QuitButton, _) -> quitGame
       Just (Button KeepPlayingButton, _) -> toggleModal KeepPlayingModal
       Just (Button StartOverButton, StartOver currentSeed siPair) -> restartGame currentSeed siPair
-      Just (Button NextButton, Next siPair) -> saveScenarioInfoOnQuit >> startGame siPair Nothing
+      Just (Button NextButton, Next siPair) -> quitGame >> startGame siPair Nothing
       _ -> return ()
   ev -> do
     Brick.zoom (uiState . uiModal . _Just . modalDialog) (handleDialogEvent ev)
@@ -419,7 +423,7 @@
                 uiState . uiGoal . listWidget .= newList
               GoalSummary -> handleInfoPanelEvent modalScroll (VtyEvent ev)
             _ -> handleInfoPanelEvent modalScroll (VtyEvent ev)
-      _ -> return ()
+      _ -> handleInfoPanelEvent modalScroll (VtyEvent ev)
    where
     refreshList lw = nestEventM' lw $ handleListEventWithSeparators ev isHeader
 
@@ -470,13 +474,28 @@
 --
 -- * writes out the updated REPL history to a @.swarm_history@ file
 -- * saves current scenario status (InProgress/Completed)
+-- * advances the menu to the next scenario IF the current one was won
 -- * returns to the previous menu
 quitGame :: EventM Name AppState ()
 quitGame = do
+  -- Write out REPL history.
   history <- use $ uiState . uiREPL . replHistory
   let hist = mapMaybe getREPLEntry $ getLatestREPLHistoryItems maxBound history
   liftIO $ (`T.appendFile` T.unlines hist) =<< getSwarmHistoryPath True
+
+  -- Save scenario status info.
   saveScenarioInfoOnQuit
+
+  -- Automatically advance the menu to the next scenario iff the
+  -- player has won the current one.
+  wc <- use $ gameState . winCondition
+  case wc of
+    WinConditions (Won _) _ -> uiState . uiMenu %= advanceMenu
+    _ -> return ()
+
+  -- Either quit the entire app (if the scenario was chosen directly
+  -- from the command line) or return to the menu (if the scenario was
+  -- chosen from the menu).
   menu <- use $ uiState . uiMenu
   case menu of
     NoMenu -> halt
@@ -759,14 +778,19 @@
       gameState . winCondition .= WinConditions (Unwinnable True) x
       openModal LoseModal
 
-      uiState . uiMenu %= advanceMenu
       return True
     WinConditions (Won False) x -> do
       -- This clears the "flag" that the Win dialog needs to pop up
       gameState . winCondition .= WinConditions (Won True) x
       openModal WinModal
 
-      uiState . uiMenu %= advanceMenu
+      -- We do NOT advance the New Game menu to the next item here (we
+      -- used to!), because we do not know if the user is going to
+      -- select 'keep playing' or 'next challenge'.  We maintain the
+      -- invariant that the current menu item is always the same as
+      -- the scenario currently being played.  If the user either (1)
+      -- quits to the menu or (2) selects 'next challenge' we will
+      -- advance the menu at that point.
       return True
     WinConditions _ oc -> do
       let newGoalTracking = GoalTracking announcementsList $ constructGoalMap isCheating oc
diff --git a/src/Swarm/TUI/Model/Menu.hs b/src/Swarm/TUI/Model/Menu.hs
--- a/src/Swarm/TUI/Model/Menu.hs
+++ b/src/Swarm/TUI/Model/Menu.hs
@@ -74,7 +74,11 @@
 data Menu
   = NoMenu -- We started playing directly from command line, no menu to show
   | MainMenu (BL.List Name MainMenuEntry)
-  | NewGameMenu (NonEmpty (BL.List Name ScenarioItem)) -- stack of scenario item lists
+  | -- Stack of scenario item lists. INVARIANT: the currently selected
+    -- menu item is ALWAYS the same as the scenario currently being played.
+    -- See https://github.com/swarm-game/swarm/issues/1064 and
+    -- https://github.com/swarm-game/swarm/pull/1065.
+    NewGameMenu (NonEmpty (BL.List Name ScenarioItem))
   | AchievementsMenu (BL.List Name CategorizedAchievement)
   | MessagesMenu
   | AboutMenu
diff --git a/swarm.cabal b/swarm.cabal
--- a/swarm.cabal
+++ b/swarm.cabal
@@ -1,6 +1,6 @@
 cabal-version:      2.4
 name:               swarm
-version:            0.3.0.0
+version:            0.3.0.1
 synopsis:           2D resource gathering game with programmable robots
 
 description:        Swarm is a 2D programming and resource gathering
diff --git a/test/integration/Main.hs b/test/integration/Main.hs
--- a/test/integration/Main.hs
+++ b/test/integration/Main.hs
@@ -24,8 +24,8 @@
 import Swarm.DocGen (EditorType (..))
 import Swarm.DocGen qualified as DocGen
 import Swarm.Game.CESK (emptyStore, initMachine)
-import Swarm.Game.Entity (EntityMap, loadEntities)
-import Swarm.Game.Robot (LogEntry, defReqs, leText, machine, robotContext, robotLog, waitingUntil)
+import Swarm.Game.Entity (EntityMap, loadEntities, lookupByName)
+import Swarm.Game.Robot (LogEntry, defReqs, equippedDevices, leText, machine, robotContext, robotLog, waitingUntil)
 import Swarm.Game.Scenario (Scenario)
 import Swarm.Game.State (
   GameState,
@@ -155,28 +155,28 @@
     "Test scenario solutions"
     [ testGroup
         "Tutorial"
-        [ testSolution Default "Tutorials/backstory"
-        , testSolution (Sec 3) "Tutorials/move"
-        , testSolution Default "Tutorials/craft"
-        , testSolution Default "Tutorials/grab"
-        , testSolution Default "Tutorials/place"
-        , testSolution Default "Tutorials/types"
-        , testSolution Default "Tutorials/type-errors"
-        , testSolution Default "Tutorials/equip"
-        , testSolution Default "Tutorials/build"
-        , testSolution Default "Tutorials/bind2"
-        , testSolution' Default "Tutorials/crash" CheckForBadErrors $ \g -> do
+        [ testTutorialSolution Default "Tutorials/backstory"
+        , testTutorialSolution (Sec 3) "Tutorials/move"
+        , testTutorialSolution Default "Tutorials/craft"
+        , testTutorialSolution Default "Tutorials/grab"
+        , testTutorialSolution Default "Tutorials/place"
+        , testTutorialSolution Default "Tutorials/types"
+        , testTutorialSolution Default "Tutorials/type-errors"
+        , testTutorialSolution Default "Tutorials/equip"
+        , testTutorialSolution Default "Tutorials/build"
+        , testTutorialSolution Default "Tutorials/bind2"
+        , testTutorialSolution' Default "Tutorials/crash" CheckForBadErrors $ \g -> do
             let rs = toList $ g ^. robotMap
             let hints = any (T.isInfixOf "you will win" . view leText) . toList . view robotLog
             let win = isJust $ find hints rs
             assertBool "Could not find a robot with winning instructions!" win
-        , testSolution Default "Tutorials/scan"
-        , testSolution Default "Tutorials/def"
-        , testSolution Default "Tutorials/lambda"
-        , testSolution Default "Tutorials/require"
-        , testSolution (Sec 3) "Tutorials/requireinv"
-        , testSolution Default "Tutorials/conditionals"
-        , testSolution (Sec 5) "Tutorials/farming"
+        , testTutorialSolution Default "Tutorials/scan"
+        , testTutorialSolution Default "Tutorials/def"
+        , testTutorialSolution Default "Tutorials/lambda"
+        , testTutorialSolution Default "Tutorials/require"
+        , testTutorialSolution (Sec 3) "Tutorials/requireinv"
+        , testTutorialSolution Default "Tutorials/conditionals"
+        , testTutorialSolution (Sec 5) "Tutorials/farming"
         ]
     , testGroup
         "Challenges"
@@ -289,6 +289,14 @@
               -- printAllLogs
               when (shouldCheckBadErrors == CheckForBadErrors) $ noBadErrors g
               verify g
+
+  tutorialHasLog :: GameState -> Assertion
+  tutorialHasLog gs =
+    let baseDevs = gs ^?! baseRobot . equippedDevices
+     in assertBool "Base should have a logger installed!" (not . null $ lookupByName "logger" baseDevs)
+
+  testTutorialSolution t f = testSolution' t f CheckForBadErrors tutorialHasLog
+  testTutorialSolution' t f s v = testSolution' t f s $ \g -> tutorialHasLog g >> v g
 
   playUntilWin :: StateT GameState IO ()
   playUntilWin = do
