[Scummvm-git-logs] scummvm master -> 68d9db488f847867c7ad4de2e5bafce013327465

athrxx noreply at scummvm.org
Tue Aug 25 20:51:42 UTC 2026


This automated email contains information about 7 new commits which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .

Summary:
4a2d193e07 KYRA: EOB: Add an optional automap overlay
1b45312111 KYRA: (EOB automap feature) - remove some code
b4624adcd6 KYRA: (EOB automap feature) - move code to new class and file
5612c62a77 KYRA: (EOB automap feature) - remove annotations
5169f77878 KYRA: (EOB automap feature) - make automap modal
94f871c455 KYRA: (EOB automap feature) - some drawing fixes
68d9db488f NEWS: move EOB automap line to the right place


Commit: 4a2d193e0776387124b70134960d9f2d88bf6faa
    https://github.com/scummvm/scummvm/commit/4a2d193e0776387124b70134960d9f2d88bf6faa
Author: Alex Djioev (adjioev at vervio.com.au)
Date: 2026-08-25T22:51:35+02:00

Commit Message:
KYRA: EOB: Add an optional automap overlay

Add an opt-out automap for Eye of the Beholder I and II:

- Tab toggles a fullscreen north-up map of the current 32x32 level, drawn
  on the overlay layer: explored vs. glimpsed cells, doors, interactive
  walls (levers, niches), floor items, the party arrow, and a side panel
  with a level-name plaque, legend and key hint. Esc closes it; the game
  keeps running underneath.
- Per-cell notes: click (or Shift+arrows) selects a cell, N edits its
  note; hovering a cell previews its note/info in the footer; Space jumps
  the selection back to the party.
- Auto-collected info, learned by observation only: stairs and teleport
  destinations are tagged when a script moves the party between levels,
  teleporter pads used within a level are paired with a shared letter,
  and teleporter pads are detected from level data on arrival.
- M toggles a mini-map drawn in place of the compass (the map is north-up
  with a party arrow, so it carries the same information); the state
  persists in ConfMan. gui_drawCompass() is the single repaint hook, so
  every path that restores the compass restores the mini-map. Not on
  SegaCD, whose play field bypasses the page it draws to.
- Map data is persisted in the saves (version 25) with bounded reads, so
  corrupt or foreign saves cannot hang the engine; a new game or an
  EOB1->EOB2 party transfer starts with an empty map.
- A launcher checkbox ("Automap", on by default) disables it all.

The repeated GUIO option lists on the EOB detection entries are also
collapsed into per-game/platform macros (GUIO_EOB1_DOS, ...).

Assisted-by: Claude:claude-opus-4-8
Co-Authored-By: Claude Fable 5 <noreply at anthropic.com>

Changed paths:
    NEWS.md
    engines/kyra/detection.h
    engines/kyra/detection_tables.h
    engines/kyra/engine/eobcommon.cpp
    engines/kyra/engine/eobcommon.h
    engines/kyra/engine/kyra_v1.cpp
    engines/kyra/engine/scene_eob.cpp
    engines/kyra/gui/gui_eob.cpp
    engines/kyra/gui/saveload.cpp
    engines/kyra/gui/saveload_eob.cpp
    engines/kyra/metaengine.cpp


diff --git a/NEWS.md b/NEWS.md
index 2248103d149..ea9eeedaa4e 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -455,6 +455,7 @@ For a more comprehensive changelog of the latest experimental code, see:
      to enable smarter replacing of thrown weapons from inventory.
    - In Eye of the Beholder 1, Ileria and Beohram NPC data fix is now behind
      a separate feature flag and launcher checkbox.
+   - Added an optional automap overlay for Eye of the Beholder 1 and 2.
 
  Lab:
    - Added keymapper support.
diff --git a/engines/kyra/detection.h b/engines/kyra/detection.h
index b55e531c634..75bd0e16f30 100644
--- a/engines/kyra/detection.h
+++ b/engines/kyra/detection.h
@@ -85,6 +85,7 @@ struct KYRAGameDescription {
 #define GAMEOPTION_EOB_ADDRULES   GUIO_GAMEOPTIONS9
 #define GAMEOPTION_EOB_RELOAD	  GUIO_GAMEOPTIONS10
 #define GAMEOPTION_EOB_NPCPATCH   GUIO_GAMEOPTIONS11
+#define GAMEOPTION_EOB_AUTOMAP    GUIO_GAMEOPTIONS12
 
 } // End of anonymous namespace
 
diff --git a/engines/kyra/detection_tables.h b/engines/kyra/detection_tables.h
index a3486bab5b6..65c8cbc28e6 100644
--- a/engines/kyra/detection_tables.h
+++ b/engines/kyra/detection_tables.h
@@ -72,6 +72,18 @@ namespace {
 #define EOB2_PC98_FLAGS FLAGS(false, false, false, false, true, false, false, false, false, Kyra::GI_EOB2)
 #define EOB2_TALKIE_FLAGS FLAGS(false, false, true, false, false, false, false, false, false, Kyra::GI_EOB2)
 
+// Shared GUIO option sets for the Eye of the Beholder games (per game and platform),
+// to avoid repeating the same long flag list on every detection entry.
+#define GUIO_EOB1_DOS        (GUIO_NOSPEECH GUIO_MIDIADLIB GUIO_MIDIPCSPK GUIO_MIDIPCJR GUIO_RENDERVGA GUIO_RENDEREGA GUIO_RENDERCGA GAMEOPTION_EOB_HPGRAPHS GAMEOPTION_EOB_MOUSESWAP GAMEOPTION_EOB_ADDRULES GAMEOPTION_EOB_RELOAD GAMEOPTION_EOB_NPCPATCH GAMEOPTION_EOB_AUTOMAP)
+#define GUIO_EOB1_AMIGA      (GUIO_NOSPEECH GUIO_MIDIAMIGA GUIO_RENDERAMIGA GAMEOPTION_EOB_HPGRAPHS GAMEOPTION_EOB_MOUSESWAP GAMEOPTION_EOB_ADDRULES GAMEOPTION_EOB_RELOAD GAMEOPTION_EOB_NPCPATCH GAMEOPTION_EOB_AUTOMAP)
+#define GUIO_EOB1_PC98       (GUIO_NOSPEECH GUIO_MIDIPC98 GUIO_RENDERPC98_16C GAMEOPTION_EOB_HPGRAPHS GAMEOPTION_EOB_MOUSESWAP GAMEOPTION_EOB_ADDRULES GAMEOPTION_EOB_RELOAD GAMEOPTION_EOB_NPCPATCH GAMEOPTION_EOB_AUTOMAP)
+#define GUIO_EOB1_SEGACD     (GUIO_NOSPEECH GUIO_MIDISEGACD GAMEOPTION_EOB_MOUSESWAP GAMEOPTION_EOB_ADDRULES GAMEOPTION_EOB_RELOAD GAMEOPTION_EOB_NPCPATCH GAMEOPTION_EOB_AUTOMAP)
+#define GUIO_EOB2_DOS        (GUIO_NOSPEECH GUIO_MIDIADLIB GUIO_MIDIPCSPK GUIO_RENDERVGA GUIO_RENDEREGA GAMEOPTION_EOB_HPGRAPHS GAMEOPTION_EOB_MOUSESWAP GAMEOPTION_EOB_ADDRULES GAMEOPTION_EOB_RELOAD GAMEOPTION_EOB_AUTOMAP)
+#define GUIO_EOB2_DOS_TALKIE (GUIO_MIDIADLIB GUIO_MIDIPCSPK GUIO_RENDERVGA GUIO_RENDEREGA GAMEOPTION_EOB_HPGRAPHS GAMEOPTION_EOB_MOUSESWAP GAMEOPTION_EOB_ADDRULES GAMEOPTION_EOB_RELOAD GAMEOPTION_EOB_AUTOMAP)
+#define GUIO_EOB2_FMTOWNS    (GUIO_NOSPEECH GUIO_MIDITOWNS GUIO_RENDERFMTOWNS GAMEOPTION_EOB_HPGRAPHS GAMEOPTION_EOB_MOUSESWAP GAMEOPTION_EOB_ADDRULES GAMEOPTION_EOB_RELOAD GAMEOPTION_EOB_AUTOMAP)
+#define GUIO_EOB2_AMIGA      (GUIO_NOSPEECH GUIO_MIDIAMIGA GUIO_RENDERAMIGA GAMEOPTION_EOB_HPGRAPHS GAMEOPTION_EOB_MOUSESWAP GAMEOPTION_EOB_ADDRULES GAMEOPTION_EOB_RELOAD GAMEOPTION_EOB_AUTOMAP)
+#define GUIO_EOB2_PC98       (GUIO_NOSPEECH GUIO_MIDIPC98 GUIO_MIDIMT32 GUIO_MIDIGM GUIO_RENDERPC98_256C GUIO_RENDERPC98_16C GAMEOPTION_EOB_HPGRAPHS GAMEOPTION_EOB_MOUSESWAP GAMEOPTION_EOB_ADDRULES GAMEOPTION_EOB_RELOAD GAMEOPTION_EOB_AUTOMAP)
+
 static const char msg_missingLangResources[]			= _s("Missing language specific game code and/or resources.");
 static const char msg_fanTrans_missingLangResources[]	= _s("Missing language specific game code and/or resources for this fan translation.");
 static const char msg_fanTrans_unsupportiveTranslator[]	= _s("The fan translator does not wish his translation to be incorporated into ScummVM.");
@@ -1990,7 +2002,7 @@ const KYRAGameDescription adGameDescs[] = {
 			Common::EN_ANY,
 			Common::kPlatformDOS,
 			ADGF_NO_FLAGS,
-			GUIO12(GUIO_NOSPEECH, GUIO_MIDIADLIB, GUIO_MIDIPCSPK, GUIO_MIDIPCJR, GUIO_RENDERVGA, GUIO_RENDEREGA, GUIO_RENDERCGA, GAMEOPTION_EOB_HPGRAPHS, GAMEOPTION_EOB_MOUSESWAP, GAMEOPTION_EOB_ADDRULES, GAMEOPTION_EOB_RELOAD, GAMEOPTION_EOB_NPCPATCH)
+			GUIO_EOB1_DOS
 		},
 		EOB_FLAGS
 	},
@@ -2003,7 +2015,7 @@ const KYRAGameDescription adGameDescs[] = {
 			Common::DE_DEU,
 			Common::kPlatformDOS,
 			ADGF_NO_FLAGS,
-			GUIO12(GUIO_NOSPEECH, GUIO_MIDIADLIB, GUIO_MIDIPCSPK, GUIO_MIDIPCJR, GUIO_RENDERVGA, GUIO_RENDEREGA, GUIO_RENDERCGA, GAMEOPTION_EOB_HPGRAPHS, GAMEOPTION_EOB_MOUSESWAP, GAMEOPTION_EOB_ADDRULES, GAMEOPTION_EOB_RELOAD, GAMEOPTION_EOB_NPCPATCH)
+			GUIO_EOB1_DOS
 		},
 		EOB_FLAGS
 	},
@@ -2016,7 +2028,7 @@ const KYRAGameDescription adGameDescs[] = {
 			Common::IT_ITA,
 			Common::kPlatformDOS,
 			ADGF_NO_FLAGS,
-			GUIO12(GUIO_NOSPEECH, GUIO_MIDIADLIB, GUIO_MIDIPCSPK, GUIO_MIDIPCJR, GUIO_RENDERVGA, GUIO_RENDEREGA, GUIO_RENDERCGA, GAMEOPTION_EOB_HPGRAPHS, GAMEOPTION_EOB_MOUSESWAP, GAMEOPTION_EOB_ADDRULES, GAMEOPTION_EOB_RELOAD, GAMEOPTION_EOB_NPCPATCH)
+			GUIO_EOB1_DOS
 		},
 		EOB_FLAGS
 	},
@@ -2029,7 +2041,7 @@ const KYRAGameDescription adGameDescs[] = {
 			Common::ES_ESP,
 			Common::kPlatformDOS,
 			ADGF_NO_FLAGS,
-			GUIO12(GUIO_NOSPEECH, GUIO_MIDIADLIB, GUIO_MIDIPCSPK, GUIO_MIDIPCJR, GUIO_RENDERVGA, GUIO_RENDEREGA, GUIO_RENDERCGA, GAMEOPTION_EOB_HPGRAPHS, GAMEOPTION_EOB_MOUSESWAP, GAMEOPTION_EOB_ADDRULES, GAMEOPTION_EOB_RELOAD, GAMEOPTION_EOB_NPCPATCH)
+			GUIO_EOB1_DOS
 		},
 		EOB_SPANISH_FLAGS
 	},
@@ -2042,7 +2054,7 @@ const KYRAGameDescription adGameDescs[] = {
 			Common::EN_ANY,
 			Common::kPlatformAmiga,
 			ADGF_NO_FLAGS,
-			GUIO8(GUIO_NOSPEECH, GUIO_MIDIAMIGA, GUIO_RENDERAMIGA, GAMEOPTION_EOB_HPGRAPHS, GAMEOPTION_EOB_MOUSESWAP, GAMEOPTION_EOB_ADDRULES, GAMEOPTION_EOB_RELOAD, GAMEOPTION_EOB_NPCPATCH)
+			GUIO_EOB1_AMIGA
 		},
 		EOB_FLAGS
 	},
@@ -2055,7 +2067,7 @@ const KYRAGameDescription adGameDescs[] = {
 			Common::DE_DEU,
 			Common::kPlatformAmiga,
 			ADGF_NO_FLAGS,
-			GUIO8(GUIO_NOSPEECH, GUIO_MIDIAMIGA, GUIO_RENDERAMIGA, GAMEOPTION_EOB_HPGRAPHS, GAMEOPTION_EOB_MOUSESWAP, GAMEOPTION_EOB_ADDRULES, GAMEOPTION_EOB_RELOAD, GAMEOPTION_EOB_NPCPATCH)
+			GUIO_EOB1_AMIGA
 		},
 		EOB_FLAGS
 	},
@@ -2068,7 +2080,7 @@ const KYRAGameDescription adGameDescs[] = {
 			Common::JA_JPN,
 			Common::kPlatformPC98,
 			ADGF_NO_FLAGS,
-			GUIO8(GUIO_NOSPEECH, GUIO_MIDIPC98, GUIO_RENDERPC98_16C, GAMEOPTION_EOB_HPGRAPHS, GAMEOPTION_EOB_MOUSESWAP, GAMEOPTION_EOB_ADDRULES, GAMEOPTION_EOB_RELOAD, GAMEOPTION_EOB_NPCPATCH)
+			GUIO_EOB1_PC98
 		},
 		EOB_PC98_FLAGS
 	},
@@ -2082,7 +2094,7 @@ const KYRAGameDescription adGameDescs[] = {
 			Common::EN_ANY,
 			Common::kPlatformSegaCD,
 			ADGF_NO_FLAGS,
-			GUIO6(GUIO_NOSPEECH, GUIO_MIDISEGACD, GAMEOPTION_EOB_MOUSESWAP, GAMEOPTION_EOB_ADDRULES, GAMEOPTION_EOB_RELOAD, GAMEOPTION_EOB_NPCPATCH)
+			GUIO_EOB1_SEGACD
 		},
 		EOB_FLAGS
 	},
@@ -2096,7 +2108,7 @@ const KYRAGameDescription adGameDescs[] = {
 			Common::JA_JPN,
 			Common::kPlatformSegaCD,
 			ADGF_NO_FLAGS,
-			GUIO6(GUIO_NOSPEECH, GUIO_MIDISEGACD, GAMEOPTION_EOB_MOUSESWAP, GAMEOPTION_EOB_ADDRULES, GAMEOPTION_EOB_RELOAD, GAMEOPTION_EOB_NPCPATCH)
+			GUIO_EOB1_SEGACD
 		},
 		EOB_FLAGS
 	},
@@ -2109,7 +2121,7 @@ const KYRAGameDescription adGameDescs[] = {
 			Common::EN_ANY,
 			Common::kPlatformDOS,
 			ADGF_NO_FLAGS,
-			GUIO9(GUIO_NOSPEECH, GUIO_MIDIADLIB, GUIO_MIDIPCSPK, GUIO_RENDERVGA, GUIO_RENDEREGA, GAMEOPTION_EOB_HPGRAPHS, GAMEOPTION_EOB_MOUSESWAP, GAMEOPTION_EOB_ADDRULES, GAMEOPTION_EOB_RELOAD)
+			GUIO_EOB2_DOS
 		},
 		EOB2_FLAGS
 	},
@@ -2122,7 +2134,7 @@ const KYRAGameDescription adGameDescs[] = {
 			Common::DE_DEU,
 			Common::kPlatformDOS,
 			ADGF_NO_FLAGS,
-			GUIO9(GUIO_NOSPEECH, GUIO_MIDIADLIB, GUIO_MIDIPCSPK, GUIO_RENDERVGA, GUIO_RENDEREGA, GAMEOPTION_EOB_HPGRAPHS, GAMEOPTION_EOB_MOUSESWAP, GAMEOPTION_EOB_ADDRULES, GAMEOPTION_EOB_RELOAD)
+			GUIO_EOB2_DOS
 		},
 		EOB2_FLAGS
 	},
@@ -2135,7 +2147,7 @@ const KYRAGameDescription adGameDescs[] = {
 			Common::ES_ESP,
 			Common::kPlatformDOS,
 			ADGF_NO_FLAGS,
-			GUIO9(GUIO_NOSPEECH, GUIO_MIDIADLIB, GUIO_MIDIPCSPK, GUIO_RENDERVGA, GUIO_RENDEREGA, GAMEOPTION_EOB_HPGRAPHS, GAMEOPTION_EOB_MOUSESWAP, GAMEOPTION_EOB_ADDRULES, GAMEOPTION_EOB_RELOAD)
+			GUIO_EOB2_DOS
 		},
 		EOB2_FLAGS
 	},
@@ -2161,7 +2173,7 @@ const KYRAGameDescription adGameDescs[] = {
 			Common::ZH_TWN,
 			Common::kPlatformDOS,
 			ADGF_NO_FLAGS,
-			GUIO8(GUIO_MIDIADLIB, GUIO_MIDIPCSPK, GUIO_RENDERVGA, GUIO_RENDEREGA, GAMEOPTION_EOB_HPGRAPHS, GAMEOPTION_EOB_MOUSESWAP, GAMEOPTION_EOB_ADDRULES, GAMEOPTION_EOB_RELOAD)
+			GUIO_EOB2_DOS_TALKIE
 		},
 		EOB2_TALKIE_FLAGS
 	},
@@ -2174,7 +2186,7 @@ const KYRAGameDescription adGameDescs[] = {
 			Common::JA_JPN,
 			Common::kPlatformFMTowns,
 			ADGF_NO_FLAGS,
-			GUIO7(GUIO_NOSPEECH, GUIO_MIDITOWNS, GUIO_RENDERFMTOWNS, GAMEOPTION_EOB_HPGRAPHS, GAMEOPTION_EOB_MOUSESWAP, GAMEOPTION_EOB_ADDRULES, GAMEOPTION_EOB_RELOAD)
+			GUIO_EOB2_FMTOWNS
 		},
 		EOB2_FMTOWNS_FLAGS
 	},
@@ -2187,7 +2199,7 @@ const KYRAGameDescription adGameDescs[] = {
 			Common::EN_ANY,
 			Common::kPlatformAmiga,
 			ADGF_NO_FLAGS,
-			GUIO7(GUIO_NOSPEECH, GUIO_MIDIAMIGA, GUIO_RENDERAMIGA, GAMEOPTION_EOB_HPGRAPHS, GAMEOPTION_EOB_MOUSESWAP, GAMEOPTION_EOB_ADDRULES, GAMEOPTION_EOB_RELOAD)
+			GUIO_EOB2_AMIGA
 		},
 		EOB2_FLAGS
 	},
@@ -2200,7 +2212,7 @@ const KYRAGameDescription adGameDescs[] = {
 			Common::DE_DEU,
 			Common::kPlatformAmiga,
 			ADGF_NO_FLAGS,
-			GUIO7(GUIO_NOSPEECH, GUIO_MIDIAMIGA, GUIO_RENDERAMIGA, GAMEOPTION_EOB_HPGRAPHS, GAMEOPTION_EOB_MOUSESWAP, GAMEOPTION_EOB_ADDRULES, GAMEOPTION_EOB_RELOAD)
+			GUIO_EOB2_AMIGA
 		},
 		EOB2_FLAGS
 	},
@@ -2216,7 +2228,7 @@ const KYRAGameDescription adGameDescs[] = {
 			Common::JA_JPN,
 			Common::kPlatformPC98,
 			ADGF_NO_FLAGS,
-			GUIO10(GUIO_NOSPEECH, GUIO_MIDIPC98, GUIO_MIDIMT32, GUIO_MIDIGM, GUIO_RENDERPC98_256C, GUIO_RENDERPC98_16C, GAMEOPTION_EOB_HPGRAPHS, GAMEOPTION_EOB_MOUSESWAP, GAMEOPTION_EOB_ADDRULES, GAMEOPTION_EOB_RELOAD)
+			GUIO_EOB2_PC98
 		},
 		EOB2_PC98_FLAGS
 	},
diff --git a/engines/kyra/engine/eobcommon.cpp b/engines/kyra/engine/eobcommon.cpp
index d5bdfa577cb..dee83b48b2d 100644
--- a/engines/kyra/engine/eobcommon.cpp
+++ b/engines/kyra/engine/eobcommon.cpp
@@ -113,6 +113,12 @@ EoBCoreEngine::EoBCoreEngine(OSystem *system, const GameFlags &flags) : KyraRpgE
 	_preventMonsterFlash = false;
 	_sceneShakeCountdown = 0;
 
+	automapReset();
+	_automapFullOpen = false;
+	_automapHudOn = false;
+	_automapEditing = false;
+	_automapNextRefresh = 0;
+
 	_teleporterPulse = 0;
 
 	_dscShapeCoords = 0;
@@ -278,6 +284,9 @@ EoBCoreEngine::~EoBCoreEngine() {
 	releaseItemsAndDecorationsShapes();
 	releaseTempData();
 
+	_automapBg.free();
+	_automapFrame.free();
+
 	if (_faceShapes) {
 		for (int i = 0; i < 44; i++) {
 			if (_characters) {
@@ -374,6 +383,11 @@ Common::KeymapArray EoBCoreEngine::initKeymaps(const Common::String &gameId) {
 	addKeymapAction(keyMap, "MVR", _("Move right"), Common::KeyState(Common::KEYCODE_RIGHT), "RIGHT", "JOY_RIGHT_TRIGGER");
 	addKeymapAction(keyMap, "TL", _("Turn left"), Common::KeyState(Common::KEYCODE_HOME), "HOME", "JOY_LEFT");
 	addKeymapAction(keyMap, "TR", _("Turn right"), Common::KeyState(Common::KEYCODE_PAGEUP), "PAGEUP", "JOY_RIGHT");
+
+	// Non-original: the automap overlay (toggle), its note editor and the mini-map HUD.
+	addKeymapAction(keyMap, "AMAP", _("Toggle automap"), Common::KeyState(Common::KEYCODE_TAB, '\t'), "TAB", "");
+	addKeymapAction(keyMap, "NOTE", _("Edit map note (while map open)"), Common::KeyState(Common::KEYCODE_n, 'n'), "n", "");
+	addKeymapAction(keyMap, "HUDM", _("Toggle mini-map (replaces the compass)"), Common::KeyState(Common::KEYCODE_m, 'm'), "m", "");
 	addKeymapAction(keyMap, "INV", _("Open / Close inventory"), Common::KeyState(Common::KEYCODE_i, 'i'), "i", "JOY_X");
 	addKeymapAction(keyMap, "SCE", _("Switch inventory / Character screen"), Common::KeyState(Common::KEYCODE_p, 'p'), "p", "JOY_Y");
 	addKeymapAction(keyMap, "CMP", _("Camp"), Common::KeyState(Common::KEYCODE_c, 'c'), "c", "");
@@ -388,6 +402,12 @@ Common::KeymapArray EoBCoreEngine::initKeymaps(const Common::String &gameId) {
 	if (gameId == "eob2")
 		addKeymapAction(keyMap, "SL6", _("Spell level 6"), Common::KeyState(Common::KEYCODE_6, '6'), "6", "");
 
+	// Non-original: Shift+arrows move the automap selection cursor (handled in runLoop).
+	addKeymapAction(keyMap, "MSU", _("Map cursor up"), Common::KeyState(Common::KEYCODE_UP, 0, Common::KBD_SHIFT), "S+UP", "");
+	addKeymapAction(keyMap, "MSD", _("Map cursor down"), Common::KeyState(Common::KEYCODE_DOWN, 0, Common::KBD_SHIFT), "S+DOWN", "");
+	addKeymapAction(keyMap, "MSL", _("Map cursor left"), Common::KeyState(Common::KEYCODE_LEFT, 0, Common::KBD_SHIFT), "S+LEFT", "");
+	addKeymapAction(keyMap, "MSR", _("Map cursor right"), Common::KeyState(Common::KEYCODE_RIGHT, 0, Common::KBD_SHIFT), "S+RIGHT", "");
+
 	return Common::Keymap::arrayOf(keyMap);
 }
 
@@ -704,6 +724,11 @@ void EoBCoreEngine::readSettings() {
 	_configADDRuleEnhancements = ConfMan.getBool("addrules");
 	_configEnhancedReload = ConfMan.getBool("mreload");
 	_configNPCPatch = (_flags.gameID == GI_EOB1) ? ConfMan.getBool("npcpatch") : false;
+	_configAutomap = ConfMan.getBool("automap");
+	// The mini-map HUD state survives restarts; SegaCD renders its play field
+	// through its own tile pipeline, so the HUD is not available there.
+	_automapHudOn = _configAutomap && _flags.platform != Common::kPlatformSegaCD &&
+		ConfMan.hasKey("automap_hud") && ConfMan.getBool("automap_hud");
 	_configSounds = ConfMan.getBool("sfx_mute") ? 0 : 1;
 	_configMusic = (_flags.platform == Common::kPlatformPC98 || _flags.platform == Common::kPlatformSegaCD) ? (ConfMan.getBool("music_mute") ? 0 : 1) : (_configSounds ? 1 : 0);
 
@@ -739,6 +764,9 @@ void EoBCoreEngine::writeSettings() {
 }
 
 void EoBCoreEngine::startupNew() {
+	// A new game must not inherit automap data, in particular from the EOB1 save
+	// that a party transfer imports via loadGameState(-1).
+	automapReset();
 	gui_setPlayFieldButtons();
 	_screen->_curPage = 0;
 	gui_drawPlayField(false);
@@ -758,11 +786,58 @@ void EoBCoreEngine::runLoop() {
 	_runFlag = true;
 
 	while (!shouldQuit() && _runFlag) {
-		uint32 frameEnd = _system->getMillis() + 8;	
+		uint32 frameEnd = _system->getMillis() + 8;
 		checkPartyStatus(true);
-		checkInput(_activeButtons, true, 0);
+		// While the full map is up, suppress the play-field buttons so clicks hit the
+		// map. The mini-map HUD is non-modal: the game plays on unchanged beneath it.
+		const bool mapFull = _automapFullOpen;
+		int inputFlag = checkInput(mapFull ? 0 : _activeButtons, true, 0);
 		removeInputTop();
 
+		if (inputFlag && inputFlag == _keyMap[Common::KEYCODE_TAB]) {
+			if (_configAutomap)         // non-original automap, opt-out via game options
+				automapToggle();
+		} else if (inputFlag && inputFlag == _keyMap[Common::KEYCODE_m] && !mapFull) {
+			if (_configAutomap)
+				automapToggleHud();     // non-original: mini-map in the compass slot
+		} else if (mapFull && inputFlag && inputFlag == _keyMap[Common::KEYCODE_ESCAPE]) {
+			automapToggle();            // Esc closes the map (only while it is open)
+		} else if (mapFull && inputFlag == 199) {
+			automapHandleClick();
+		} else if (mapFull && inputFlag && inputFlag == _keyMap[Common::KEYCODE_n]) {
+			automapEditNote();
+		} else if (mapFull && inputFlag && inputFlag == _keyMap[Common::KEYCODE_SPACE]) {
+			// Space jumps the selection back to the party.
+			_automapSelectedBlock = _currentBlock;
+		// Shift+arrows move the map selection cursor (place notes without a mouse);
+		// the 0x100 tag makes each value non-zero, so no separate inputFlag guard.
+		} else if (mapFull && inputFlag == (_keyMap[Common::KEYCODE_UP] | 0x100)) {
+			automapMoveSelection(0, -1);
+		} else if (mapFull && inputFlag == (_keyMap[Common::KEYCODE_DOWN] | 0x100)) {
+			automapMoveSelection(0, 1);
+		} else if (mapFull && inputFlag == (_keyMap[Common::KEYCODE_LEFT] | 0x100)) {
+			automapMoveSelection(-1, 0);
+		} else if (mapFull && inputFlag == (_keyMap[Common::KEYCODE_RIGHT] | 0x100)) {
+			automapMoveSelection(1, 0);
+		} else if (mapFull && inputFlag) {
+			// Movement is normally button-driven, but those are suppressed now, so
+			// dispatch the arrow/turn keys here (the handlers only read button->index,
+			// so a dummy is fine); any other key falls through untouched.
+			Button dummy;
+			if (inputFlag == _keyMap[Common::KEYCODE_UP])
+				clickedUpArrow(&dummy);
+			else if (inputFlag == _keyMap[Common::KEYCODE_DOWN])
+				clickedDownArrow(&dummy);
+			else if (inputFlag == _keyMap[Common::KEYCODE_LEFT])
+				clickedLeftArrow(&dummy);
+			else if (inputFlag == _keyMap[Common::KEYCODE_RIGHT])
+				clickedRightArrow(&dummy);
+			else if (inputFlag == _keyMap[Common::KEYCODE_HOME])
+				clickedTurnLeftArrow(&dummy);
+			else if (inputFlag == _keyMap[Common::KEYCODE_PAGEUP])
+				clickedTurnRightArrow(&dummy);
+		}
+
 		if (!_runFlag)
 			break;
 
@@ -770,8 +845,29 @@ void EoBCoreEngine::runLoop() {
 		updateScriptTimers();
 		updateWallOfForceTimers();
 
-		if (_sceneUpdateRequired && !_sceneShakeCountdown)
+		bool sceneRedraw = _sceneUpdateRequired && !_sceneShakeCountdown;
+		// On any view change, mark the now-visible blocks "seen" for the map.
+		if (sceneRedraw)
+			automapMarkSeenFromCurrent();
+
+		if (_automapFullOpen) {
+			// Opaque overlay: skip the dungeon redraw behind it (redrawn on close).
+			_sceneUpdateRequired = false;
+			// Redraw on activity only, plus a slow heartbeat so passive changes
+			// (live floor items, an overlay resize) still show up.
+			bool mapRedraw = inputFlag || sceneRedraw || _system->getMillis() >= _automapNextRefresh;
+			const uint16 hover = automapBlockAtMouse();
+			if (hover != _automapHoverBlock) {
+				_automapHoverBlock = hover; // hovered cell's info shows in the footer
+				mapRedraw = true;
+			}
+			if (mapRedraw) {
+				automapDraw();
+				_automapNextRefresh = _system->getMillis() + 250;
+			}
+		} else if (sceneRedraw) {
 			drawScene(1);
+		}
 
 		updatePlayTimer();
 		updateAnimations();
diff --git a/engines/kyra/engine/eobcommon.h b/engines/kyra/engine/eobcommon.h
index 7df78fd2be8..df89529df5b 100644
--- a/engines/kyra/engine/eobcommon.h
+++ b/engines/kyra/engine/eobcommon.h
@@ -652,6 +652,87 @@ protected:
 	void drawSceneShapes(int start = 0, int end = 18, int drawFlags = 0xFF);
 	void drawDecorations(int index);
 
+	// Automap (non-original): a north-up map toggled with Tab. "Visited" cells were
+	// walked, "seen" cells only glimpsed (drawn dimmer); one bit each, per level.
+	void automapSetBit(uint8 (&bits)[20][128], uint16 block) {
+		if (_currentLevel < 20 && block < 1024)
+			bits[_currentLevel][block >> 3] |= (1 << (block & 7));
+	}
+	bool automapGetBit(const uint8 (&bits)[20][128], uint16 block) const {
+		return _currentLevel < 20 && block < 1024 && (bits[_currentLevel][block >> 3] & (1 << (block & 7)));
+	}
+	void automapMarkVisited(uint16 block) { automapSetBit(_automapVisited, block); }
+	bool automapIsVisited(uint16 block) const { return automapGetBit(_automapVisited, block); }
+	void automapMarkSeen(uint16 block) { automapSetBit(_automapSeen, block); }
+	bool automapIsSeen(uint16 block) const { return automapGetBit(_automapSeen, block); }
+	// True when the party can step off `block` in direction `d` (the neighbour's
+	// facing wall, dir^2, is passable) - the wall/movement test the map draws with.
+	bool automapSideOpen(uint16 block, int d) {
+		const uint16 nb = calcNewBlockPosition(block, d);
+		return (_wllWallFlags[_levelBlockProperties[nb].walls[d ^ 2]] & 1) != 0;
+	}
+	// A door leaf sits on both walls of its passage axis, so test either wall of the
+	// pair (N/S = walls 0,2; E/W = walls 1,3). Live state only - open doors animate.
+	bool automapDoorNS(const LevelBlockProperty *bp) const { return (_wllWallFlags[bp->walls[0]] & 8) || (_wllWallFlags[bp->walls[2]] & 8); }
+	bool automapDoorEW(const LevelBlockProperty *bp) const { return (_wllWallFlags[bp->walls[1]] & 8) || (_wllWallFlags[bp->walls[3]] & 8); }
+	void automapMarkSeenFromCurrent();
+	void automapReset();
+	void automapToggle();    // Tab / Esc: fullscreen map overlay
+	void automapToggleHud(); // M: mini-map in place of the compass (persisted in ConfMan)
+	void automapDraw();
+	void automapDrawMini();
+
+	// Geometry shared by drawing and click hit-testing.
+	struct AutomapLayout {
+		int cell;
+		int offX, offY;                 // top-left of the 32x32 grid (mouse hit-test)
+		int frame;                      // stone frame thickness
+		int mapX, mapY, mapW, mapH;     // parchment region (map + footer strip)
+		int sideX, sideY, sideW, sideH; // stone side panel (plaque, legend, keys)
+		int plX, plY, plW, plH;         // level/coords plaque inside the side panel
+		int footY;                      // top of the selected-note footer strip
+	};
+	AutomapLayout automapLayout() const;
+	// Area name for the plaque; falls back to "Level N" where unknown.
+	Common::String automapLevelName() const;
+	uint32 automapNoteKey(uint16 block) const { return ((uint32)_currentLevel << 16) | block; }
+	// Per-cell map notes (non-original): free text, keyed per level. Click selects, N edits.
+	uint16 automapBlockAtMouse() const;
+	void automapHandleClick();
+	void automapEditNote();
+
+	// Auto-collected per-cell info (separate from manual notes): a glyph code plus a
+	// short description, learned by observation. Floor items are read live, not stored.
+	enum AutomapIcon { kAmNone = 0, kAmStairsDown = 1, kAmStairsUp = 2, kAmTeleport = 3 };
+	void automapCollectCellInfo(uint16 block);
+	void automapTagTransition(int fromLevel, uint16 fromBlock, int toLevel);
+	void automapLinkTeleport(uint16 fromBlock, uint16 toBlock);
+	Common::String automapLiveItems(uint16 block) const;
+	void automapDrawIcon(Graphics::Surface &surf, int sx, int sy, int cell, uint8 icon, uint32 color) const;
+	// Cached static chrome, rebuilt only on overlay resize; _automapFrame is the
+	// reused per-frame compositing surface (so the draw loop allocates nothing).
+	Graphics::Surface _automapBg;
+	Graphics::Surface _automapFrame;
+
+	uint8 _automapVisited[20][128];
+	uint8 _automapSeen[20][128];
+	bool _automapFullOpen; // fullscreen map overlay shown (modal)
+	bool _automapHudOn;    // mini-map drawn in the compass slot (non-modal)
+	Common::HashMap<uint32, Common::String> _automapNotes;
+	Common::HashMap<uint32, uint8> _automapIcons;
+	Common::HashMap<uint32, Common::String> _automapAutoInfo;
+	// Teleporter pads paired by use (same letter on both ends), keyed like the notes.
+	Common::HashMap<uint32, uint8> _automapTeleLinks;
+	uint16 _automapSelectedBlock;
+	uint16 _automapHoverBlock; // cell under the mouse (transient); its info shows in the footer
+	uint32 _automapNextRefresh; // next passive redraw (live floor items etc.); input redraws at once
+	bool _automapEditing;
+	Common::String _automapEditBuffer;
+	// Transient per-cell door bits (1 = N/S leaf, 2 = E/W), so an open door (which
+	// loses its wall flag while animating) still draws. Rebuilt each draw, not saved.
+	Common::HashMap<uint32, uint8> _automapDoorBits;
+	void automapMoveSelection(int dx, int dy);
+
 	int calcNewBlockPositionAndTestPassability(uint16 curBlock, uint16 direction);
 	void notifyBlockNotPassable();
 	void increaseStepsCounter();
@@ -857,6 +938,7 @@ protected:
 	bool _configADDRuleEnhancements;
 	bool _configEnhancedReload;
 	bool _configNPCPatch;
+	bool _configAutomap; // non-original: in-game automap overlay enabled (opt-out)
 
 	Graphics::Surface _thumbNail;
 
diff --git a/engines/kyra/engine/kyra_v1.cpp b/engines/kyra/engine/kyra_v1.cpp
index eceb6f9d075..560d2a08411 100644
--- a/engines/kyra/engine/kyra_v1.cpp
+++ b/engines/kyra/engine/kyra_v1.cpp
@@ -379,6 +379,7 @@ void KyraEngine_v1::setupKeyMap() {
 		{ KC(F4), 115, 102, 96 },
 		{ KC(F5), 116, 103, 97 },
 		{ KC(F6), 117, 104, 98 },
+		{ KC(TAB), 118, 118, 118 },
 		{ KC(a), 31, 31, UNKNOWN_KEYCODE },
 		{ KC(b), 50, 50, 66 },
 		{ KC(c), 48, 48, 67 },
diff --git a/engines/kyra/engine/scene_eob.cpp b/engines/kyra/engine/scene_eob.cpp
index fe87c29e5dd..d18305918ef 100644
--- a/engines/kyra/engine/scene_eob.cpp
+++ b/engines/kyra/engine/scene_eob.cpp
@@ -751,6 +751,12 @@ void EoBCoreEngine::moveParty(uint16 block) {
 	updateAllMonsterDests();
 	uint16 old = _currentBlock;
 	_currentBlock = block;
+	automapMarkVisited(block);
+
+	// Note the level before running scripts: if one moves the party elsewhere, the
+	// automap tags this cell as an exit.
+	const int preLevel = _currentLevel;
+	const uint16 trigBlock = block;
 
 	runLevelScript(old, 2);
 
@@ -761,6 +767,13 @@ void EoBCoreEngine::moveParty(uint16 block) {
 
 	runLevelScript(block, 1);
 
+	if (_currentLevel != preLevel)
+		automapTagTransition(preLevel, trigBlock, _currentLevel);
+	else if (_currentBlock != trigBlock)
+		automapLinkTeleport(trigBlock, _currentBlock); // a script moved us within the level
+	else
+		automapCollectCellInfo(block);
+
 	if (_flags.gameID == GI_EOB2 && _levelBlockProperties[block].walls[0] == 26)
 		memset(_levelBlockProperties[block].walls, 0, 4);
 
diff --git a/engines/kyra/gui/gui_eob.cpp b/engines/kyra/gui/gui_eob.cpp
index 185a5b7f5f9..98aac32ca20 100644
--- a/engines/kyra/gui/gui_eob.cpp
+++ b/engines/kyra/gui/gui_eob.cpp
@@ -30,9 +30,13 @@
 #include "kyra/graphics/screen_eob_segacd.h"
 
 #include "backends/keymapper/keymapper.h"
+#include "common/config-manager.h"
 #include "common/system.h"
 #include "common/savefile.h"
 #include "graphics/scaler.h"
+#include "graphics/surface.h"
+#include "graphics/font.h"
+#include "graphics/fontman.h"
 
 namespace Kyra {
 
@@ -643,6 +647,15 @@ void EoBCoreEngine::gui_drawCharacterStatsPage() {
 }
 
 void EoBCoreEngine::gui_drawCompass(bool force) {
+	// Non-original: with the mini-map HUD on, the map takes the compass' place
+	// (it is north-up with a party arrow, so it carries the same information).
+	// Every path that would repaint the compass repaints the map instead.
+	if (_automapHudOn && _configAutomap && _flags.platform != Common::kPlatformSegaCD) {
+		automapDrawMini();
+		_compassDirection = _currentDirection;
+		return;
+	}
+
 	if (_currentDirection == _compassDirection && !force)
 		return;
 
@@ -4971,6 +4984,940 @@ const uint8 GUI_EoB::_highlightColorTablePC98[] = { 0x0C, 0x0D, 0x0E, 0x0F, 0x0E
 
 const uint8 GUI_EoB::_highlightColorTableSegaCD[] = { 0x3D, 0x3D, 0x3D, 0x3E, 0x3E, 0x3E, 0x3F, 0x3F, 0x3F, 0x3E, 0x3E, 0x3E, 0x00 };
 
+// Automap (non-original): a north-up map of the 32x32 level grid (block = (y<<5)|x),
+// drawn over the 3D view and toggled with Tab.
+
+// Forget all collected automap data (new game, party transfer, before a load).
+void EoBCoreEngine::automapReset() {
+	memset(_automapVisited, 0, sizeof(_automapVisited));
+	memset(_automapSeen, 0, sizeof(_automapSeen));
+	_automapNotes.clear();
+	_automapIcons.clear();
+	_automapAutoInfo.clear();
+	_automapTeleLinks.clear();
+	_automapDoorBits.clear();
+	_automapSelectedBlock = 0xFFFF;
+	_automapHoverBlock = 0xFFFF;
+}
+
+void EoBCoreEngine::automapMarkSeenFromCurrent() {
+	// Mark the cells in the line of sight ahead (not side cells), up to the first wall.
+	const int dir = _currentDirection;
+	uint16 block = _currentBlock;
+	for (int depth = 1; depth <= 2; ++depth) {
+		// Passability via the neighbour's facing wall (walls[dir ^ 2]), as movement uses.
+		const uint16 nb = calcNewBlockPosition(block, dir);
+		if (!(_wllWallFlags[_levelBlockProperties[nb].walls[dir ^ 2]] & 1))
+			break;
+		block = nb;
+		automapMarkSeen(block);
+	}
+}
+
+static void automapFillTri(Graphics::Surface &s, int ax, int ay, int bx, int by, int cx, int cy, uint32 color);
+
+// Comma-separated names of the items currently on `block`. "" if none.
+Common::String EoBCoreEngine::automapLiveItems(uint16 block) const {
+	if (block >= 1024)
+		return Common::String();
+	Common::String out;
+	const uint16 head = _levelBlockProperties[block].drawObjects;
+	uint16 o = head;
+	int guard = 0;
+	while (o && guard++ < 64) {
+		const EoBItem *it = &_items[o];
+		const char *nm = _itemNames[it->nameUnid];
+		if (nm && *nm) {
+			if (!out.empty())
+				out += ", ";
+			out += nm;
+		}
+		o = it->next;
+		if (o == head) // the floor list is circular
+			break;
+	}
+	return out;
+}
+
+// On arrival, flag a teleporter pad. Stairs/destinations are learned in automapTagTransition.
+void EoBCoreEngine::automapCollectCellInfo(uint16 block) {
+	if (_currentLevel >= 20 || block >= 1024)
+		return;
+	const uint32 k = automapNoteKey(block);
+	if (_automapIcons.contains(k)) // already classified (e.g. as stairs)
+		return;
+	for (int d = 0; d < 4; ++d) {
+		if (_levelBlockProperties[block].walls[d] == _teleporterWallId) {
+			_automapIcons[k] = kAmTeleport;
+			if (!_automapAutoInfo.contains(k))
+				_automapAutoInfo[k] = "Teleporter";
+			break;
+		}
+	}
+}
+
+// On a script-driven level change, tag the cell we left with a stairs/teleport
+// glyph and a "to level N" note (deeper level = down).
+void EoBCoreEngine::automapTagTransition(int fromLevel, uint16 fromBlock, int toLevel) {
+	if (fromLevel < 0 || fromLevel >= 20 || fromBlock >= 1024)
+		return;
+	const uint32 k = ((uint32)fromLevel << 16) | fromBlock;
+	uint8 icon;
+	Common::String info;
+	if (toLevel > fromLevel) {
+		icon = kAmStairsDown;
+		info = Common::String::format("Down to level %d", toLevel);
+	} else if (toLevel < fromLevel) {
+		icon = kAmStairsUp;
+		info = Common::String::format("Up to level %d", toLevel);
+	} else {
+		icon = kAmTeleport;
+		info = Common::String::format("Teleport to level %d", toLevel);
+	}
+	_automapIcons[k] = icon;
+	_automapAutoInfo[k] = info;
+}
+
+// A script moved the party within the level: pair the source and destination pads
+// with a shared letter (learned only by actually using the teleporter).
+void EoBCoreEngine::automapLinkTeleport(uint16 fromBlock, uint16 toBlock) {
+	if (_currentLevel >= 20 || fromBlock >= 1024 || toBlock >= 1024 || fromBlock == toBlock)
+		return;
+	const uint32 kf = automapNoteKey(fromBlock);
+	const uint32 kt = automapNoteKey(toBlock);
+	int id = -1;
+	if (_automapTeleLinks.contains(kf))
+		id = _automapTeleLinks[kf];
+	else if (_automapTeleLinks.contains(kt))
+		id = _automapTeleLinks[kt];
+	if (id < 0) {
+		// First unused pair letter on this level.
+		for (Common::HashMap<uint32, uint8>::const_iterator it = _automapTeleLinks.begin(); it != _automapTeleLinks.end(); ++it) {
+			if ((int)(it->_key >> 16) == _currentLevel)
+				id = MAX<int>(id, it->_value);
+		}
+		if (++id > 25) // out of letters: keep the plain glyph
+			return;
+	}
+	_automapTeleLinks[kf] = _automapTeleLinks[kt] = (uint8)id;
+	if (!_automapIcons.contains(kf))
+		_automapIcons[kf] = kAmTeleport;
+	if (!_automapIcons.contains(kt))
+		_automapIcons[kt] = kAmTeleport;
+	const Common::String info = Common::String::format("Teleport %c", 'A' + id);
+	_automapAutoInfo[kf] = info;
+	_automapAutoInfo[kt] = info;
+}
+
+void EoBCoreEngine::automapDrawIcon(Graphics::Surface &surf, int sx, int sy, int cell, uint8 icon, uint32 color) const {
+	const int cx = sx + cell / 2;
+	const int cy = sy + cell / 2;
+	const int r = MAX(2, cell / 3);
+	switch (icon) {
+	case kAmStairsDown:
+		automapFillTri(surf, cx - r, cy - r, cx + r, cy - r, cx, cy + r, color);
+		break;
+	case kAmStairsUp:
+		automapFillTri(surf, cx - r, cy + r, cx + r, cy + r, cx, cy - r, color);
+		break;
+	case kAmTeleport:
+		surf.frameRect(Common::Rect(cx - r, cy - r, cx + r, cy + r), color);
+		surf.frameRect(Common::Rect(cx - r + 1, cy - r + 1, cx + r - 1, cy + r - 1), color);
+		break;
+	default:
+		break;
+	}
+}
+
+// Scaled text: render at native font size, then nearest-neighbour upscale.
+static void automapDrawBigString(Graphics::Surface &dst, const Graphics::Font *font,
+		const Common::String &text, int x, int y, int w, uint32 color, int scale,
+		Graphics::TextAlign align = Graphics::kTextAlignCenter) {
+	if (!font || text.empty() || scale < 1)
+		return;
+	const int tw = font->getStringWidth(text);
+	const int th = font->getFontHeight();
+	if (tw <= 0 || th <= 0)
+		return;
+
+	if (scale == 1) {
+		font->drawString(&dst, text, x, y, w, color, align);
+		return;
+	}
+
+	// The overlay may be 16- or 32-bit; index pixels by byte width.
+	const int bpp = dst.format.bytesPerPixel;
+	if (bpp != 2 && bpp != 4) { // unexpected depth: fall back to native size
+		font->drawString(&dst, text, x, y, w, color, align);
+		return;
+	}
+
+	// Render onto a zeroed scratch surface so inked pixels are distinguishable.
+	Graphics::Surface tmp;
+	tmp.create(tw, th, dst.format);
+	tmp.fillRect(Common::Rect(0, 0, tw, th), 0);
+	font->drawString(&tmp, text, 0, 0, tw, color, Graphics::kTextAlignLeft);
+
+	const int dw = tw * scale;
+	int dx = x + (w - dw) / 2; // center (default)
+	if (align == Graphics::kTextAlignLeft)
+		dx = x;
+	else if (align == Graphics::kTextAlignRight)
+		dx = x + w - dw;
+	if (dx < x)
+		dx = x;
+	for (int sy = 0; sy < th; ++sy) {
+		const byte *src = (const byte *)tmp.getBasePtr(0, sy);
+		for (int sx = 0; sx < tw; ++sx) {
+			const uint32 c = (bpp == 2) ? ((const uint16 *)src)[sx] : ((const uint32 *)src)[sx];
+			if (c == 0) // transparent background pixel
+				continue;
+			const int px = dx + sx * scale;
+			const int py = y + sy * scale;
+			dst.fillRect(Common::Rect(px, py, px + scale, py + scale), c);
+		}
+	}
+	tmp.free();
+}
+
+void EoBCoreEngine::automapToggle() {
+	_automapFullOpen = !_automapFullOpen;
+	if (_automapFullOpen) {
+		_automapSelectedBlock = _currentBlock;
+		_automapHoverBlock = 0xFFFF;
+		// inGUI=true => getMousePos() is in overlay coords, matching the drawn grid.
+		_system->showOverlay(true);
+	} else {
+		_system->hideOverlay();
+		_sceneUpdateRequired = true; // repaint the dungeon the overlay hid
+	}
+}
+
+// M: mini-map in the compass slot (the map is north-up with a party arrow, so it
+// covers the compass' job). The state is a config setting, so it survives restarts.
+void EoBCoreEngine::automapToggleHud() {
+	if (_flags.platform == Common::kPlatformSegaCD)
+		return; // the SegaCD play field is rendered through its own tile pipeline
+	_automapHudOn = !_automapHudOn;
+	ConfMan.setBool("automap_hud", _automapHudOn);
+	if (_automapHudOn) {
+		const int cp = _screen->setCurPage(0);
+		gui_drawCompass(true); // the compass hook draws the mini-map now
+		_screen->setCurPage(cp);
+	} else {
+		// Restore the play-field bitmap (and thus the compass) under the mini-map.
+		gui_drawPlayField(false);
+		gui_drawAllCharPortraitsWithStats();
+		_sceneUpdateRequired = true;
+	}
+	_screen->updateScreen();
+}
+
+// Solid triangle by scanlines (party arrow).
+static void automapFillTri(Graphics::Surface &s, int ax, int ay, int bx, int by, int cx, int cy, uint32 color) {
+	// Sort vertices by y.
+	if (ay > by) { SWAP(ax, bx); SWAP(ay, by); }
+	if (ay > cy) { SWAP(ax, cx); SWAP(ay, cy); }
+	if (by > cy) { SWAP(bx, cx); SWAP(by, cy); }
+	if (cy == ay)
+		return;
+
+	for (int y = ay; y <= cy; ++y) {
+		int xLong = ax + (cx - ax) * (y - ay) / (cy - ay);
+		int xShort = (y < by)
+			? (by == ay ? ax : ax + (bx - ax) * (y - ay) / (by - ay))
+			: (cy == by ? bx : bx + (cx - bx) * (y - by) / (cy - by));
+		int x0 = MIN(xLong, xShort);
+		int x1 = MAX(xLong, xShort);
+		s.hLine(x0, y, x1, color);
+	}
+}
+
+// Geometry shared by drawing and click hit-testing (so clicks hit the drawn cell).
+EoBCoreEngine::AutomapLayout EoBCoreEngine::automapLayout() const {
+	const int ow = _system->getOverlayWidth();
+	const int oh = _system->getOverlayHeight();
+	AutomapLayout l;
+
+	// Stone frame around the overlay, a side panel on the right, and a parchment map
+	// inset filling the rest; the 32x32 grid is centred with a footer strip below.
+	l.frame = CLIP<int>(oh / 36, 8, 28);
+	const int gap = MAX(4, l.frame / 2);
+	l.sideW = MAX(150, ow / 4);
+
+	const int inX = l.frame, inY = l.frame;
+	const int inW = ow - 2 * l.frame, inH = oh - 2 * l.frame;
+
+	l.mapX = inX;
+	l.mapY = inY;
+	l.mapW = inW - l.sideW - gap;
+	l.mapH = inH;
+	l.sideX = inX + l.mapW + gap;
+	l.sideY = inY;
+	l.sideH = inH;
+
+	const int mpad = MAX(8, l.mapW / 40);
+	const int footStrip = MAX(18, oh / 22);
+	const int availW = l.mapW - 2 * mpad;
+	const int availH = l.mapH - 2 * mpad - footStrip;
+	l.cell = MAX(2, MIN(availW / 32, availH / 32));
+	const int grid = l.cell * 32;
+	l.offX = l.mapX + (l.mapW - grid) / 2;
+	l.offY = l.mapY + mpad + (availH - grid) / 2;
+	l.footY = l.mapY + l.mapH - footStrip;
+
+	const int spad = MAX(6, l.sideW / 12);
+	l.plX = l.sideX + spad;
+	l.plY = l.sideY + spad;
+	l.plW = l.sideW - 2 * spad;
+	l.plH = MAX(30, oh / 8);
+	return l;
+}
+
+// Area name for the plaque, indexed by internal level number (EOB has no names in
+// its data). EOB2's numbering is offset (the forest is level 4); the names were
+// derived from each LEVELn.INF's wall-set/palette, not walkthrough play-order.
+Common::String EoBCoreEngine::automapLevelName() const {
+	static const char *const eob1[12] = {
+		"Upper Sewers", "Middle Sewers", "Lower Sewers",
+		"Dwarven Ruins I", "Dwarven Camp", "Dwarven Ruins III",
+		"Upper Drow", "Drow Outcasts", "Lower Drow",
+		"Mantis Hive", "Xanathar Sanctum", "Xanathar's Lair"
+	};
+	static const char *const eob2[16] = {
+		"Catacombs I", "Catacombs II", "Catacombs III", "Forest",
+		"Darkmoon Temple", "Temple Mezzanine", "Silver Tower I", "Silver Tower II",
+		"Silver Tower III", "Azure Tower I", "Azure Tower II", "Azure Tower III",
+		"Azure Tower IV", "Azure Tower V", "Crimson Tower I", "Crimson Tower II"
+	};
+	const int idx = _currentLevel - 1;
+	const char *name = nullptr;
+	if (_flags.gameID == GI_EOB1) {
+		if (idx >= 0 && idx < 12)
+			name = eob1[idx];
+	} else if (_flags.gameID == GI_EOB2) {
+		if (idx >= 0 && idx < 16)
+			name = eob2[idx];
+	}
+	if (name)
+		return Common::String(name);
+	return Common::String::format("Level %d", _currentLevel);
+}
+
+// Move the selection cursor (Shift+arrows), clamped to the grid. Like the mouse
+// path, the cursor only lands on discovered cells - otherwise the selection frame
+// would sit on empty parchment where N silently does nothing.
+void EoBCoreEngine::automapMoveSelection(int dx, int dy) {
+	const uint16 cur = (_automapSelectedBlock != 0xFFFF) ? _automapSelectedBlock : _currentBlock;
+	const int bx = CLIP<int>((cur & 0x1F) + dx, 0, 31);
+	const int by = CLIP<int>((cur >> 5) + dy, 0, 31);
+	const uint16 dest = (by << 5) | bx;
+	if (automapIsVisited(dest) || automapIsSeen(dest))
+		_automapSelectedBlock = dest;
+}
+
+// Discovered grid cell under the mouse, 0xFFFF when outside or still unexplored.
+uint16 EoBCoreEngine::automapBlockAtMouse() const {
+	const Common::Point p = _eventMan->getMousePos();
+	const AutomapLayout l = automapLayout();
+	if (p.x < l.offX || p.y < l.offY)
+		return 0xFFFF;
+	const int bx = (p.x - l.offX) / l.cell;
+	const int by = (p.y - l.offY) / l.cell;
+	if (bx >= 32 || by >= 32)
+		return 0xFFFF;
+	const uint16 block = (by << 5) | bx;
+	return (automapIsVisited(block) || automapIsSeen(block)) ? block : 0xFFFF;
+}
+
+// A click selects the cell (editing is a separate step, N), so the map can be browsed.
+void EoBCoreEngine::automapHandleClick() {
+	const uint16 block = automapBlockAtMouse();
+	if (block != 0xFFFF)
+		_automapSelectedBlock = block;
+}
+
+// Note editor for the selected cell. Disables the game keymap so letters arrive as
+// raw ASCII (as GUI_EoB::getTextInput does); Enter saves (empty deletes), Esc cancels.
+void EoBCoreEngine::automapEditNote() {
+	uint16 block = (_automapSelectedBlock != 0xFFFF) ? _automapSelectedBlock : _currentBlock;
+	if (!(automapIsVisited(block) || automapIsSeen(block)))
+		return;
+	_automapSelectedBlock = block;
+
+	Common::Keymap *km = _eventMan->getKeymapper()->getKeymap(kKeymapName);
+	km->setEnabled(false);
+
+	const uint32 key = automapNoteKey(block);
+	_automapEditBuffer = _automapNotes.contains(key) ? _automapNotes[key] : Common::String();
+	_automapEditing = true;
+
+	const uint kMaxLen = 40;
+	bool done = false, cancel = false;
+	bool redraw = true;
+	uint32 caretPhase = 0xFFFFFFFF;
+	while (!done && !shouldQuit()) {
+		// Redraw only after a keystroke or when the blinking caret flips phase.
+		const uint32 phase = (_system->getMillis() / 400) & 1;
+		if (redraw || phase != caretPhase) {
+			automapDraw();
+			caretPhase = phase;
+			redraw = false;
+		}
+		updateInput();
+		for (Common::List<KyraEngine_v1::Event>::const_iterator e = _eventList.begin(); e != _eventList.end(); ++e) {
+			if (e->event.type != Common::EVENT_KEYDOWN)
+				continue;
+			redraw = true;
+			const Common::KeyCode kc = e->event.kbd.keycode;
+			const uint16 asc = e->event.kbd.ascii;
+			if (kc == Common::KEYCODE_RETURN || kc == Common::KEYCODE_KP_ENTER)
+				done = true;
+			else if (kc == Common::KEYCODE_ESCAPE)
+				done = cancel = true;
+			else if (kc == Common::KEYCODE_BACKSPACE) {
+				if (!_automapEditBuffer.empty())
+					_automapEditBuffer.deleteLastChar();
+			} else if (asc >= 32 && asc < 127 && _automapEditBuffer.size() < kMaxLen) {
+				_automapEditBuffer += (char)asc;
+			}
+		}
+		_eventList.clear();
+		_system->delayMillis(12);
+	}
+
+	if (!cancel) {
+		_automapEditBuffer.trim();
+		if (_automapEditBuffer.empty())
+			_automapNotes.erase(key);
+		else
+			_automapNotes[key] = _automapEditBuffer;
+	}
+
+	_automapEditing = false;
+	_automapEditBuffer.clear();
+	km->setEnabled(true);
+}
+
+// Sparse two-tone speckle to fake stone/paper grain (overwrites pixels, no blend).
+static void automapNoise(Graphics::Surface &s, int x0, int y0, int w, int h, uint32 cA, uint32 cB, uint32 seed) {
+	if (w <= 0 || h <= 0 || x0 < 0 || y0 < 0 || x0 + w > s.w || y0 + h > s.h)
+		return;
+	const int bpp = s.format.bytesPerPixel;
+	if (bpp != 2 && bpp != 4)
+		return;
+	uint32 st = seed | 1u;
+	for (int y = 0; y < h; ++y) {
+		byte *row = (byte *)s.getBasePtr(x0, y0 + y);
+		for (int x = 0; x < w; ++x) {
+			st = st * 1664525u + 1013904223u;
+			const uint32 v = st >> 24; // 0..255
+			if (v < 26) {
+				const uint32 c = (v & 1u) ? cA : cB;
+				if (bpp == 2)
+					((uint16 *)row)[x] = (uint16)c;
+				else
+					((uint32 *)row)[x] = c;
+			}
+		}
+	}
+}
+
+static void automapBevel(Graphics::Surface &s, const Common::Rect &r, uint32 hi, uint32 lo) {
+	s.hLine(r.left, r.top, r.right - 1, hi);
+	s.vLine(r.left, r.top, r.bottom - 1, hi);
+	s.hLine(r.left, r.bottom - 1, r.right - 1, lo);
+	s.vLine(r.right - 1, r.top, r.bottom - 1, lo);
+}
+
+static void automapRivet(Graphics::Surface &s, int x, int y, int sz, uint32 dark, uint32 mid, uint32 hi) {
+	s.fillRect(Common::Rect(x - sz, y - sz, x + sz + 1, y + sz + 1), dark);
+	s.fillRect(Common::Rect(x - sz + 1, y - sz + 1, x + sz, y + sz), mid);
+	const int q = MAX(1, sz / 2);
+	s.fillRect(Common::Rect(x - sz + 1, y - sz + 1, x - sz + 1 + q, y - sz + 1 + q), hi);
+}
+
+static int automapFit(const Graphics::Font *f, const Common::String &str, int maxW, int maxSc) {
+	int sc = MAX(1, maxSc);
+	while (sc > 1 && f && f->getStringWidth(str) * sc > maxW)
+		--sc;
+	return sc;
+}
+
+void EoBCoreEngine::automapDraw() {
+	automapMarkVisited(_currentBlock);
+
+	const int ow = _system->getOverlayWidth();
+	const int oh = _system->getOverlayHeight();
+	const Graphics::PixelFormat fmt = _system->getOverlayFormat();
+	const AutomapLayout L = automapLayout();
+
+	// "Stone tablet" palette: opaque grey stone + aged parchment (names self-explain).
+	const uint32 cStone     = fmt.RGBToColor(0x54, 0x56, 0x5e);
+	const uint32 cStoneDark = fmt.RGBToColor(0x3c, 0x3e, 0x45);
+	const uint32 cStoneEdge = fmt.RGBToColor(0x23, 0x25, 0x2a);
+	const uint32 cStoneHi   = fmt.RGBToColor(0x6b, 0x6e, 0x78);
+	const uint32 cRivet     = fmt.RGBToColor(0xc2, 0xc5, 0xcd);
+	const uint32 cPaper     = fmt.RGBToColor(0xd6, 0xbf, 0x94);
+	const uint32 cPaperHi   = fmt.RGBToColor(0xe2, 0xcd, 0xa4);
+	const uint32 cPaperLo   = fmt.RGBToColor(0xc4, 0xab, 0x78);
+	const uint32 cPaperEdge = fmt.RGBToColor(0xa9, 0x8b, 0x56);
+	const uint32 cInk       = fmt.RGBToColor(0x3a, 0x2a, 0x18);
+	const uint32 cInkSoft   = fmt.RGBToColor(0x7a, 0x60, 0x38);
+	const uint32 cFloor     = fmt.RGBToColor(0xbb, 0x9c, 0x5e); // a touch darker than paper
+	const uint32 cFloorSeen = fmt.RGBToColor(0xc7, 0xb1, 0x80);
+	const uint32 cGrid      = fmt.RGBToColor(0x8a, 0x70, 0x38);
+	const uint32 cWall      = fmt.RGBToColor(0x2c, 0x1e, 0x10);
+	const uint32 cWallSeen  = fmt.RGBToColor(0x8a, 0x73, 0x4a);
+	const uint32 cDoor      = fmt.RGBToColor(0x7a, 0x4a, 0x1c);
+	const uint32 cStair     = fmt.RGBToColor(0x3f, 0x7a, 0x3a);
+	const uint32 cTele      = fmt.RGBToColor(0x5a, 0x3f, 0x8a);
+	const uint32 cLever     = fmt.RGBToColor(0x9a, 0x2d, 0x2d);
+	const uint32 cNiche     = fmt.RGBToColor(0x8a, 0x5a, 0x1c);
+	const uint32 cNote      = fmt.RGBToColor(0x2f, 0x4d, 0x8f); // "pen ink" blue, distinct from the brown marks
+	const uint32 cParty     = fmt.RGBToColor(0xa8, 0x28, 0x1c);
+	const uint32 cPartyEdge = fmt.RGBToColor(0x5a, 0x14, 0x0e);
+	const uint32 cSel       = fmt.RGBToColor(0xe8, 0xb8, 0x30); // gold frame (per mockup), not another red
+	const uint32 cPlaqueBg  = fmt.RGBToColor(0x1c, 0x1a, 0x16);
+	const uint32 cPlaqueEd  = fmt.RGBToColor(0x0d, 0x0c, 0x0a);
+	const uint32 cGold      = fmt.RGBToColor(0xf0, 0xc8, 0x50);
+	const uint32 cGoldDim   = fmt.RGBToColor(0xb8, 0x92, 0x3a);
+	const uint32 cPanelTxt  = fmt.RGBToColor(0xd8, 0xc8, 0xa8);
+
+	const Graphics::Font *bigFont = FontMan.getFontByUsage(Graphics::FontManager::kBigGUIFont);
+	const int fh = bigFont ? bigFont->getFontHeight() : 8;
+	const int sc = CLIP<int>(oh / 320, 1, 3); // base side-panel text scale
+	const int mpad = MAX(8, L.mapW / 40);
+
+	// (Re)build the cached chrome on an overlay resize or pixel-format change
+	// (free() zeroes w/h, so an empty surface never matches).
+	if (_automapBg.w != ow || _automapBg.h != oh || _automapBg.format != fmt) {
+		_automapBg.free();
+		_automapBg.create(ow, oh, fmt);
+		_automapFrame.free();
+		_automapFrame.create(ow, oh, fmt);
+		Graphics::Surface &bg = _automapBg;
+
+		bg.fillRect(Common::Rect(0, 0, ow, oh), cStone);
+		automapNoise(bg, 0, 0, ow, oh, cStoneHi, cStoneEdge, 0x9e3779b9u);
+		automapBevel(bg, Common::Rect(0, 0, ow, oh), cStoneHi, cStoneEdge);
+		automapBevel(bg, Common::Rect(2, 2, ow - 2, oh - 2), cStone, cStoneEdge);
+		const int rv = MAX(2, L.frame / 4);
+		const int ri = L.frame / 2;
+		automapRivet(bg, ri, ri, rv, cStoneEdge, cStoneHi, cRivet);
+		automapRivet(bg, ow - ri, ri, rv, cStoneEdge, cStoneHi, cRivet);
+		automapRivet(bg, ri, oh - ri, rv, cStoneEdge, cStoneHi, cRivet);
+		automapRivet(bg, ow - ri, oh - ri, rv, cStoneEdge, cStoneHi, cRivet);
+
+		const Common::Rect mr(L.mapX, L.mapY, L.mapX + L.mapW, L.mapY + L.mapH);
+		bg.fillRect(mr, cPaperEdge);
+		bg.fillRect(Common::Rect(mr.left + 2, mr.top + 2, mr.right - 2, mr.bottom - 2), cPaper);
+		automapNoise(bg, mr.left + 2, mr.top + 2, L.mapW - 4, L.mapH - 4, cPaperHi, cPaperLo, 0x85ebca6bu);
+		bg.frameRect(Common::Rect(mr.left + 2, mr.top + 2, mr.right - 2, mr.bottom - 2), cInkSoft);
+		bg.frameRect(Common::Rect(mr.left + 5, mr.top + 5, mr.right - 5, mr.bottom - 5), cInk);
+		bg.hLine(L.mapX + mpad, L.footY, L.mapX + L.mapW - mpad, cInkSoft);
+
+		const Common::Rect sr(L.sideX, L.sideY, L.sideX + L.sideW, L.sideY + L.sideH);
+		bg.fillRect(sr, cStoneDark);
+		automapNoise(bg, sr.left, sr.top, L.sideW, L.sideH, cStoneHi, cStoneEdge, 0xc2b2ae35u);
+		automapBevel(bg, sr, cStoneEdge, cStoneHi); // sunken: dark top/left
+
+		// Plaque face only; its text is drawn per frame.
+		bg.fillRect(Common::Rect(L.plX, L.plY, L.plX + L.plW, L.plY + L.plH), cPlaqueEd);
+		bg.fillRect(Common::Rect(L.plX + 2, L.plY + 2, L.plX + L.plW - 2, L.plY + L.plH - 2), cPlaqueBg);
+
+		const int lx = L.plX, colW = L.plW;
+		const int rowH = fh * sc + MAX(4, sc * 4);
+		int cyy = L.plY + L.plH + MAX(8, sc * 6);
+
+		automapDrawBigString(bg, bigFont, "- LEGEND -", lx, cyy, colW, cGoldDim, sc);
+		cyy += fh * sc + MAX(4, sc * 3);
+		bg.hLine(lx, cyy, lx + colW - 1, cStoneHi);
+		bg.hLine(lx, cyy + 1, lx + colW - 1, cStoneEdge);
+		cyy += MAX(6, sc * 4);
+
+		// Legend rows are drawn with the same primitives as the map cells, on mini
+		// paper swatches, so what the player compares against matches the map. The
+		// double-slot rows show both variants (stairs up/down, pad/paired, lever/niche).
+		static const char *const legendLbl[9] = {
+			"Explored", "Glimpsed", "Party", "Stairs", "Teleporter", "Door", "Lever / Niche", "Loot", "Note"
+		};
+		const int isz = MAX(8, fh * sc);
+		const int slotGap = MAX(2, sc);
+		const int wt2 = MAX(1, isz / 5); // mini wall thickness
+		const int panelBottom = L.sideY + L.sideH - MAX(6, L.sideW / 12);
+		for (int i = 0; i < 9; ++i) {
+			if (cyy + rowH > panelBottom)
+				break; // tiny window: drop the tail rather than draw past the panel
+			const int iy = cyy;
+			const int slots = (i == 3 || i == 4 || i == 6) ? 2 : 1;
+			for (int s = 0; s < slots; ++s) {
+				const int ix = lx + s * (isz + slotGap);
+				bg.fillRect(Common::Rect(ix, iy, ix + isz, iy + isz), (i == 1) ? cFloorSeen : cFloor);
+				switch (i) {
+				case 0: // explored: grid corner like a visited cell
+					bg.hLine(ix, iy, ix + isz - 1, cGrid);
+					bg.vLine(ix, iy, iy + isz - 1, cGrid);
+					break;
+				case 1: // glimpsed: paler floor, dim wall
+					bg.fillRect(Common::Rect(ix, iy, ix + isz, iy + wt2), cWallSeen);
+					break;
+				case 2:
+					automapFillTri(bg, ix + isz / 2, iy + 1, ix + isz - 1, iy + isz - 1, ix + 1, iy + isz - 1, cParty);
+					break;
+				case 3: // stairs up / down
+					if (s == 0)
+						automapFillTri(bg, ix + 1, iy + isz - 1, ix + isz - 1, iy + isz - 1, ix + isz / 2, iy + 1, cStair);
+					else
+						automapFillTri(bg, ix + 1, iy + 1, ix + isz - 1, iy + 1, ix + isz / 2, iy + isz - 1, cStair);
+					break;
+				case 4: // teleporter pad; used pads pair up and show a letter instead
+					if (s == 0) {
+						bg.frameRect(Common::Rect(ix + 1, iy + 1, ix + isz - 1, iy + isz - 1), cTele);
+						bg.frameRect(Common::Rect(ix + 2, iy + 2, ix + isz - 2, iy + isz - 2), cTele);
+					} else if (bigFont && fh <= isz) {
+						automapDrawBigString(bg, bigFont, "A", ix, iy + (isz - fh) / 2, isz, cTele, 1);
+					}
+					break;
+				case 5: // door leaf across the cell
+					bg.fillRect(Common::Rect(ix + 1, iy + (isz - wt2) / 2, ix + isz - 1, iy + (isz - wt2) / 2 + wt2 + 1), cDoor);
+					break;
+				case 6: { // lever / niche: pip on the carrying wall edge
+					const int ps = MAX(2, isz / 3);
+					bg.fillRect(Common::Rect(ix + (isz - ps) / 2, iy, ix + (isz + ps) / 2, iy + ps), (s == 0) ? cLever : cNiche);
+					break;
+				}
+				case 7: { // loot dot, bottom-left like the map
+					const int ls2 = MAX(2, isz / 3);
+					bg.fillRect(Common::Rect(ix + 1, iy + isz - ls2 - 1, ix + 1 + ls2, iy + isz - 1), cGold);
+					break;
+				}
+				default: { // note dot, top-right corner like the map
+					const int ds = MAX(2, isz / 3);
+					bg.fillRect(Common::Rect(ix + isz - ds, iy, ix + isz, iy + ds), cNote);
+					break;
+				}
+				}
+			}
+			const int iconW = 2 * isz + slotGap; // fixed two-slot column keeps labels aligned
+			const int tx = lx + iconW + MAX(4, sc * 2);
+			const int tw = colW - iconW - MAX(4, sc * 2);
+			const int lsc = automapFit(bigFont, legendLbl[i], tw, sc);
+			automapDrawBigString(bg, bigFont, legendLbl[i], tx, iy + (isz - fh * lsc) / 2, tw, cPanelTxt, lsc, Graphics::kTextAlignLeft);
+			cyy += rowH;
+		}
+
+		// KEYS: a single N chip (the rest is hinted in the empty footer strip).
+		const int chipPad = MAX(2, sc * 2);
+		const int chipH = fh * sc + chipPad;
+		const int keysH = MAX(6, sc * 5) + fh * sc + MAX(4, sc * 3) + 2 + MAX(6, sc * 4) + chipH;
+		if (cyy + keysH <= panelBottom) {
+			cyy += MAX(6, sc * 5);
+			automapDrawBigString(bg, bigFont, "- KEYS -", lx, cyy, colW, cGoldDim, sc);
+			cyy += fh * sc + MAX(4, sc * 3);
+			bg.hLine(lx, cyy, lx + colW - 1, cStoneHi);
+			bg.hLine(lx, cyy + 1, lx + colW - 1, cStoneEdge);
+			cyy += MAX(6, sc * 4);
+			const int chipW = (bigFont ? bigFont->getStringWidth("N") * sc : 6 * sc) + chipPad * 2;
+			bg.fillRect(Common::Rect(lx, cyy, lx + chipW, cyy + chipH), cPlaqueEd);
+			bg.fillRect(Common::Rect(lx + 1, cyy + 1, lx + chipW - 1, cyy + chipH - 1), cPlaqueBg);
+			automapDrawBigString(bg, bigFont, "N", lx, cyy + chipPad / 2, chipW, cGold, sc);
+			automapDrawBigString(bg, bigFont, "Add note", lx + chipW + MAX(4, sc * 3), cyy + (chipH - fh * sc) / 2,
+				colW - chipW - MAX(4, sc * 3), cPanelTxt, sc, Graphics::kTextAlignLeft);
+		}
+	}
+
+	// Per frame: copy the chrome into the reused surface, then draw dynamics on top.
+	Graphics::Surface &surf = _automapFrame;
+	surf.copyRectToSurface(_automapBg, 0, 0, Common::Rect(0, 0, ow, oh));
+
+	const int cell = L.cell;
+	const int offX = L.offX;
+	const int offY = L.offY;
+	const int wt = MAX(2, cell / 5); // wall bar thickness
+
+	for (int by = 0; by < 32; ++by) {
+		for (int bx = 0; bx < 32; ++bx) {
+			const uint16 block = (by << 5) | bx;
+			const bool visited = automapIsVisited(block);
+			const bool seen = !visited && automapIsSeen(block);
+			if (!visited && !seen)
+				continue;
+
+			const int sx = offX + bx * cell;
+			const int sy = offY + by * cell;
+
+			// A side is a wall only where the party can't step (neighbour's facing wall, dir^2).
+			bool wall[4];
+			for (int d = 0; d < 4; ++d)
+				wall[d] = !automapSideOpen(block, d);
+
+			// Cache the door bits: an open door loses its wall flag mid-animation but
+			// should still draw as a door.
+			const LevelBlockProperty *bp = &_levelBlockProperties[block];
+			const uint32 bkey = automapNoteKey(block);
+			uint8 dbits = _automapDoorBits.contains(bkey) ? _automapDoorBits[bkey] : 0;
+			if (automapDoorNS(bp))
+				dbits |= 1;
+			if (automapDoorEW(bp))
+				dbits |= 2;
+			if (dbits)
+				_automapDoorBits[bkey] = dbits;
+			const bool doorNS = (dbits & 1) != 0;
+			const bool doorEW = (dbits & 2) != 0;
+			if (doorNS)
+				wall[0] = wall[2] = false;
+			if (doorEW)
+				wall[1] = wall[3] = false;
+
+			surf.fillRect(Common::Rect(sx, sy, sx + cell, sy + cell), visited ? cFloor : cFloorSeen);
+			if (visited) {
+				surf.hLine(sx, sy, sx + cell - 1, cGrid);
+				surf.vLine(sx, sy, sy + cell - 1, cGrid);
+			}
+			const uint32 wc = visited ? cWall : cWallSeen;
+			if (wall[0]) surf.fillRect(Common::Rect(sx, sy, sx + cell, sy + wt), wc);
+			if (wall[1]) surf.fillRect(Common::Rect(sx + cell - wt, sy, sx + cell, sy + cell), wc);
+			if (wall[2]) surf.fillRect(Common::Rect(sx, sy + cell - wt, sx + cell, sy + cell), wc);
+			if (wall[3]) surf.fillRect(Common::Rect(sx, sy, sx + wt, sy + cell), wc);
+
+			if (doorNS || doorEW) {
+				const int dt = MAX(2, wt + 1);
+				const int dm = MAX(1, cell / 5);
+				const int dcx = sx + cell / 2, dcy = sy + cell / 2;
+				if (doorNS)
+					surf.fillRect(Common::Rect(sx + dm, dcy - dt / 2, sx + cell - dm, dcy - dt / 2 + dt), cDoor);
+				if (doorEW)
+					surf.fillRect(Common::Rect(dcx - dt / 2, sy + dm, dcx - dt / 2 + dt, sy + cell - dm), cDoor);
+			}
+
+			// Special-wall pip on the carrying side (skip door types 5/6; 10 = niche).
+			for (int d = 0; d < 4; ++d) {
+				const uint8 st = _specialWallTypes[bp->walls[d]];
+				if (st == 0 || st == 5 || st == 6)
+					continue;
+				const uint32 pcol = (st == 10) ? cNiche : cLever;
+				const int ps = MAX(2, cell / 4);
+				int px, py;
+				switch (d) {
+				case 0:  px = sx + cell / 2 - ps / 2; py = sy; break;
+				case 1:  px = sx + cell - ps;         py = sy + cell / 2 - ps / 2; break;
+				case 2:  px = sx + cell / 2 - ps / 2; py = sy + cell - ps; break;
+				default: px = sx;                     py = sy + cell / 2 - ps / 2; break;
+				}
+				surf.fillRect(Common::Rect(px, py, px + ps, py + ps), pcol);
+			}
+
+			// Paired teleporters show a matching letter on both ends (replacing the
+			// glyph); when the cell is too small for the font, keep the glyph.
+			const bool linkLetter = _automapTeleLinks.contains(bkey) && bigFont && fh + 2 <= cell;
+			if (_automapIcons.contains(bkey) && !linkLetter) {
+				const uint8 ic = _automapIcons[bkey];
+				uint32 col = (ic == kAmTeleport) ? cTele : cStair;
+				if (!visited)
+					col = cWallSeen;
+				automapDrawIcon(surf, sx, sy, cell, ic, col);
+			}
+			if (linkLetter) {
+				automapDrawBigString(surf, bigFont, Common::String::format("%c", 'A' + (_automapTeleLinks[bkey] % 26)),
+					sx, sy + (cell - fh) / 2, cell, visited ? cTele : cWallSeen, 1);
+			}
+
+			if (_automapNotes.contains(bkey)) {
+				const int ds = MAX(2, cell / 3);
+				surf.fillRect(Common::Rect(sx + cell - ds, sy, sx + cell, sy + ds), cNote);
+			}
+
+			// Loot dot (bottom-left): items lying on this cell, read live.
+			if (bp->drawObjects && !automapLiveItems(block).empty()) {
+				const int ls = MAX(2, cell / 4);
+				surf.fillRect(Common::Rect(sx + 1, sy + cell - ls - 1, sx + 1 + ls, sy + cell - 1), cGold);
+			}
+		}
+	}
+
+	if (_automapSelectedBlock != 0xFFFF) {
+		const int sx = offX + (_automapSelectedBlock & 0x1F) * cell;
+		const int sy = offY + (_automapSelectedBlock >> 5) * cell;
+		surf.frameRect(Common::Rect(sx, sy, sx + cell, sy + cell), cSel);
+		surf.frameRect(Common::Rect(sx + 1, sy + 1, sx + cell - 1, sy + cell - 1), cSel);
+	}
+
+	const int mx = offX + (_currentBlock & 0x1F) * cell;
+	const int my = offY + (_currentBlock >> 5) * cell;
+	const int cx = mx + cell / 2;
+	const int cyp = my + cell / 2;
+	const int r = MAX(2, cell / 2 - MAX(1, cell / 6));
+	const int b = MAX(2, r * 3 / 4);
+	int tipX, tipY, l1X, l1Y, l2X, l2Y;
+	switch (_currentDirection) {
+	case 1:  tipX = cx + r; tipY = cyp; l1X = cx - b; l1Y = cyp - b; l2X = cx - b; l2Y = cyp + b; break;
+	case 2:  tipX = cx; tipY = cyp + r; l1X = cx - b; l1Y = cyp - b; l2X = cx + b; l2Y = cyp - b; break;
+	case 3:  tipX = cx - r; tipY = cyp; l1X = cx + b; l1Y = cyp - b; l2X = cx + b; l2Y = cyp + b; break;
+	default: tipX = cx; tipY = cyp - r; l1X = cx - b; l1Y = cyp + b; l2X = cx + b; l2Y = cyp + b; break;
+	}
+	automapFillTri(surf, tipX, tipY, l1X, l1Y, l2X, l2Y, cPartyEdge);
+	const int gx = (tipX + l1X + l2X) / 3, gy = (tipY + l1Y + l2Y) / 3;
+	automapFillTri(surf,
+		tipX + (gx - tipX) / 4, tipY + (gy - tipY) / 4,
+		l1X + (gx - l1X) / 4, l1Y + (gy - l1Y) / 4,
+		l2X + (gx - l2X) / 4, l2Y + (gy - l2Y) / 4, cParty);
+
+	// Plaque text: area name over coords, in two size-fitted bands so they never overlap.
+	if (bigFont) {
+		const uint16 cb = (_automapHoverBlock != 0xFFFF) ? _automapHoverBlock
+			: ((_automapSelectedBlock != 0xFFFF) ? _automapSelectedBlock : _currentBlock);
+		const Common::String lvl = automapLevelName();
+		const Common::String crd = Common::String::format("X %d   Y %d", cb & 0x1F, cb >> 5);
+		const int pm = MAX(3, sc * 2);
+		const int innerW = L.plW - 2 * pm;
+		const int innerH = L.plH - 2 * pm;
+		const int gap = MAX(2, innerH / 12);
+		const int lvlBand = (innerH - gap) * 6 / 10; // level gets ~60%, coords ~40%
+		const int crdBand = (innerH - gap) - lvlBand;
+		const int lvlSc = automapFit(bigFont, lvl, innerW, MAX(1, lvlBand / fh));
+		const int crdSc = automapFit(bigFont, crd, innerW, MAX(1, crdBand / fh));
+		const int lvlY = L.plY + pm + (lvlBand - fh * lvlSc) / 2;
+		const int crdY = L.plY + pm + lvlBand + gap + (crdBand - fh * crdSc) / 2;
+		automapDrawBigString(surf, bigFont, lvl, L.plX, lvlY, L.plW, cGold, lvlSc);
+		automapDrawBigString(surf, bigFont, crd, L.plX, crdY, L.plW, cPanelTxt, crdSc);
+	}
+
+	// Footer: the selected cell's note / auto-info (the KEYS panel documents N).
+	if (bigFont) {
+		Common::String foot;
+		if (_automapEditing) {
+			const char *caret = ((_system->getMillis() / 400) & 1) ? "_" : " ";
+			foot = Common::String("Note: ") + _automapEditBuffer + caret;
+		} else {
+			// The hovered cell's info previews in the footer; otherwise the selected one's.
+			const uint16 ib = (_automapHoverBlock != 0xFFFF) ? _automapHoverBlock : _automapSelectedBlock;
+			if (ib != 0xFFFF) {
+				// A note must not hide what the map learned: show note, auto-info
+				// and floor items together.
+				const uint32 ik = automapNoteKey(ib);
+				if (_automapNotes.contains(ik))
+					foot = _automapNotes[ik];
+				if (_automapAutoInfo.contains(ik))
+					foot = foot.empty() ? _automapAutoInfo[ik] : foot + " - " + _automapAutoInfo[ik];
+				const Common::String items = automapLiveItems(ib);
+				if (!items.empty())
+					foot = foot.empty() ? items : foot + " - " + items;
+			}
+		}
+		uint32 footCol = cInk;
+		if (foot.empty()) {
+			// Idle footer doubles as the key hint (keeps the side panel to just N).
+			foot = "Click a cell - N note - SPACE find party - M mini-map";
+			footCol = cInkSoft;
+		}
+		{
+			const int footH = (L.mapY + L.mapH) - L.footY;
+			const int fpad = MAX(2, footH / 6);
+			const int maxFsc = MAX(1, (footH - 2 * fpad) / fh);
+			const int fw = L.mapW - 2 * mpad;
+			const int fsc = MIN(automapFit(bigFont, foot, fw, sc), maxFsc);
+			const int fy = L.footY + (footH - fh * fsc) / 2;
+			automapDrawBigString(surf, bigFont, foot, L.mapX + mpad, fy, fw, footCol, fsc, Graphics::kTextAlignLeft);
+		}
+	}
+
+	_system->copyRectToOverlay(surf.getPixels(), surf.pitch, 0, 0, ow, oh);
+	_system->updateScreen();
+}
+
+// Mini-map HUD (toggled with M): an 11x11 window around the party drawn over the
+// compass dome in palette colors while the game runs on. Reached only through
+// gui_drawCompass(), so every path that repaints the compass repaints the map.
+void EoBCoreEngine::automapDrawMini() {
+	if (_currentLevel < 1 || _currentLevel >= 20)
+		return;
+	automapMarkVisited(_currentBlock);
+
+	const KyraRpgGUISettings::Colors &c = guiSettings()->colors;
+	const uint8 colFloor = (c.fill >= 0) ? (uint8)c.fill : c.frame2;
+	const uint8 colSeen  = c.frame2;
+	const uint8 colWall  = c.guiColorWhite; // bright wall outlines keep the cell grid readable
+	const uint8 colDoor  = c.guiColorBrown;
+	const uint8 colParty = c.guiColorLightRed;
+	const uint8 colTick  = c.guiColorWhite;
+	const uint8 colStair = c.guiColorLightGreen;
+	const uint8 colTele  = c.guiColorLightBlue;
+	const uint8 colNote  = c.guiColorYellow;
+
+	// An opaque panel lined up with the surrounding chrome: from the movement-arrow
+	// block (ends at x=66, see the button defs) to the view port's right edge
+	// (x=175), and from the view port's bottom row down over the whole compass dome
+	// incl. the needle mount. Staying below y=120 keeps it clear of view-port blits.
+	const int cell = 4, radiusX = 12, radiusY = 5;
+	// The ZH_TWN text box starts at y=170, so don't reach under it there.
+	const int bw = 108, bh = (_flags.lang == Common::ZH_TWN) ? 50 : 56;
+	const int bx0 = 68;
+	const int by0 = 120;
+	const int x0 = bx0 + (bw - (2 * radiusX + 1) * cell) / 2;
+	const int y0 = by0 + (bh - (2 * radiusY + 1) * cell) / 2;
+	const int px = _currentBlock & 0x1F, py = _currentBlock >> 5;
+
+	// Draw to the current page, exactly like the compass shapes this replaces
+	// (gui_drawPlayField composes on page 2, the scene path draws on page 0).
+	const int pg = _screen->_curPage;
+	gui_drawBox(bx0, by0, bw, bh, c.frame1, c.frame2, c.guiColorBlack);
+
+	for (int dy = -radiusY; dy <= radiusY; ++dy) {
+		for (int dx = -radiusX; dx <= radiusX; ++dx) {
+			const int bx = px + dx, by = py + dy;
+			if (bx < 0 || bx > 31 || by < 0 || by > 31)
+				continue;
+			const uint16 block = (by << 5) | bx;
+			const bool visited = automapIsVisited(block);
+			if (!visited && !automapIsSeen(block))
+				continue;
+			const int sx = x0 + (dx + radiusX) * cell;
+			const int sy = y0 + (dy + radiusY) * cell;
+			_screen->fillRect(sx, sy, sx + cell - 1, sy + cell - 1, visited ? colFloor : colSeen, pg);
+
+			// Same wall/door tests as the full map, at 1px scale.
+			const LevelBlockProperty *bp = &_levelBlockProperties[block];
+			const bool doorNS = automapDoorNS(bp);
+			const bool doorEW = automapDoorEW(bp);
+			for (int d = 0; d < 4; ++d) {
+				if (((d == 0 || d == 2) && doorNS) || ((d == 1 || d == 3) && doorEW))
+					continue;
+				if (automapSideOpen(block, d))
+					continue;
+				switch (d) {
+				case 0:  _screen->fillRect(sx, sy, sx + cell - 1, sy, colWall, pg); break;
+				case 1:  _screen->fillRect(sx + cell - 1, sy, sx + cell - 1, sy + cell - 1, colWall, pg); break;
+				case 2:  _screen->fillRect(sx, sy + cell - 1, sx + cell - 1, sy + cell - 1, colWall, pg); break;
+				default: _screen->fillRect(sx, sy, sx, sy + cell - 1, colWall, pg); break;
+				}
+			}
+			if (doorNS)
+				_screen->fillRect(sx, sy + cell / 2, sx + cell - 1, sy + cell / 2, colDoor, pg);
+			if (doorEW)
+				_screen->fillRect(sx + cell / 2, sy, sx + cell / 2, sy + cell - 1, colDoor, pg);
+
+			const uint32 bkey = automapNoteKey(block);
+			if (_automapIcons.contains(bkey)) {
+				const uint8 icol = (_automapIcons[bkey] == kAmTeleport) ? colTele : colStair;
+				_screen->fillRect(sx + 1, sy + 1, sx + cell - 2, sy + cell - 2, icol, pg);
+			} else if (_automapNotes.contains(bkey)) {
+				_screen->fillRect(sx + 1, sy + 1, sx + cell - 2, sy + cell - 2, colNote, pg);
+			}
+		}
+	}
+
+	// Party cell, with a facing tick on the leading edge.
+	const int psx = x0 + radiusX * cell, psy = y0 + radiusY * cell;
+	_screen->fillRect(psx, psy, psx + cell - 1, psy + cell - 1, colParty, pg);
+	switch (_currentDirection) {
+	case 1:  _screen->fillRect(psx + cell - 1, psy + 1, psx + cell - 1, psy + cell - 2, colTick, pg); break;
+	case 2:  _screen->fillRect(psx + 1, psy + cell - 1, psx + cell - 2, psy + cell - 1, colTick, pg); break;
+	case 3:  _screen->fillRect(psx, psy + 1, psx, psy + cell - 2, colTick, pg); break;
+	default: _screen->fillRect(psx + 1, psy, psx + cell - 2, psy, colTick, pg); break;
+	}
+}
+
 } // End of namespace Kyra
 
 #endif // ENABLE_EOB
diff --git a/engines/kyra/gui/saveload.cpp b/engines/kyra/gui/saveload.cpp
index cdcdca5b32b..05794c77317 100644
--- a/engines/kyra/gui/saveload.cpp
+++ b/engines/kyra/gui/saveload.cpp
@@ -28,7 +28,7 @@
 #include "graphics/thumbnail.h"
 #include "graphics/surface.h"
 
-#define CURRENT_SAVE_VERSION 24
+#define CURRENT_SAVE_VERSION 25
 
 #define GF_FLOPPY  (1 <<  0)
 #define GF_TALKIE  (1 <<  1)
diff --git a/engines/kyra/gui/saveload_eob.cpp b/engines/kyra/gui/saveload_eob.cpp
index da0020fb798..4115aa577f1 100644
--- a/engines/kyra/gui/saveload_eob.cpp
+++ b/engines/kyra/gui/saveload_eob.cpp
@@ -34,6 +34,53 @@
 
 namespace Kyra {
 
+// Bounds for the automap records read from a save. Real data stays far below both
+// (20 levels x 1024 blocks; notes are capped at 40 chars when typed), so hitting a
+// bound means a corrupt/foreign file - stop reading rather than hang or OOM.
+enum {
+	kAutomapMaxEntries = 20 * 1024,
+	kAutomapMaxStrLen = 256
+};
+
+// Automap key->string maps: a count, then key + null-terminated string per entry.
+static void automapReadStringMap(Common::ReadStream &in, Common::HashMap<uint32, Common::String> &map) {
+	uint32 n = in.readUint32BE();
+	if (in.eos() || n > kAutomapMaxEntries)
+		return;
+	for (uint32 i = 0; i < n && !in.eos(); ++i) {
+		uint32 k = in.readUint32BE();
+		map[k] = in.readString(0, kAutomapMaxStrLen);
+	}
+}
+
+// Automap key->byte maps (glyph codes, teleporter pair ids): count, then key + byte.
+static void automapReadByteMap(Common::ReadStream &in, Common::HashMap<uint32, uint8> &map) {
+	uint32 n = in.readUint32BE();
+	if (in.eos() || n > kAutomapMaxEntries)
+		return;
+	for (uint32 i = 0; i < n && !in.eos(); ++i) {
+		uint32 k = in.readUint32BE();
+		map[k] = in.readByte();
+	}
+}
+
+static void automapWriteByteMap(Common::WriteStream *out, const Common::HashMap<uint32, uint8> &map) {
+	out->writeUint32BE(map.size());
+	for (Common::HashMap<uint32, uint8>::const_iterator it = map.begin(); it != map.end(); ++it) {
+		out->writeUint32BE(it->_key);
+		out->writeByte(it->_value);
+	}
+}
+
+static void automapWriteStringMap(Common::WriteStream *out, const Common::HashMap<uint32, Common::String> &map) {
+	out->writeUint32BE(map.size());
+	for (Common::HashMap<uint32, Common::String>::const_iterator it = map.begin(); it != map.end(); ++it) {
+		out->writeUint32BE(it->_key);
+		out->writeString(it->_value);
+		out->writeByte(0);
+	}
+}
+
 Common::Error EoBCoreEngine::loadGameState(int slot) {
 	// Special slot id -1 for EOB1 party transfer
 	const char *fileName = (slot == -1) ? _savegameFilename.c_str() : getSavegameFilename(slot);
@@ -280,6 +327,18 @@ Common::Error EoBCoreEngine::loadGameState(int slot) {
 		}
 	}
 
+	// Automap data (save version >= 25). Clear first (incl. the transient door-bit
+	// cache, which must not carry over) and only read when the save is new enough.
+	automapReset();
+	if (header.version >= 25) {
+		in.read(_automapVisited, sizeof(_automapVisited));
+		in.read(_automapSeen, sizeof(_automapSeen));
+		automapReadStringMap(in, _automapNotes);
+		automapReadByteMap(in, _automapIcons);
+		automapReadStringMap(in, _automapAutoInfo);
+		automapReadByteMap(in, _automapTeleLinks);
+	}
+
 	loadLevel(_currentLevel, _currentSub);
 	if (_flags.platform == Common::kPlatformFMTowns && _gameToLoad != -1)
 		_screen->setScreenPalette(_screen->getPalette(0));
@@ -538,6 +597,15 @@ Common::Error EoBCoreEngine::saveGameStateIntern(int slot, const char *saveName,
 		}
 	}
 
+	// Automap data (save version >= 25): visited/seen bitfields, notes, glyphs,
+	// info, teleporter pair ids.
+	out->write(_automapVisited, sizeof(_automapVisited));
+	out->write(_automapSeen, sizeof(_automapSeen));
+	automapWriteStringMap(out, _automapNotes);
+	automapWriteByteMap(out, _automapIcons);
+	automapWriteStringMap(out, _automapAutoInfo);
+	automapWriteByteMap(out, _automapTeleLinks);
+
 	out->finalize();
 
 	// check for errors
diff --git a/engines/kyra/metaengine.cpp b/engines/kyra/metaengine.cpp
index 525ad6d833e..a1e91600da5 100644
--- a/engines/kyra/metaengine.cpp
+++ b/engines/kyra/metaengine.cpp
@@ -193,6 +193,18 @@ const ADExtraGuiOptionsMap gameGuiOptions[] = {
 		}
 	},
 
+	{
+		GAMEOPTION_EOB_AUTOMAP,
+		{
+			_s("Automap"),
+			_s("Enable the in-game automap overlay (toggle in-game with Tab)"),
+			"automap",
+			true,
+			0,
+			0
+		}
+	},
+
 	AD_EXTRA_GUI_OPTIONS_TERMINATOR
 };
 


Commit: 1b453121116ac07aa4154c0109b08ed68bd2e11a
    https://github.com/scummvm/scummvm/commit/1b453121116ac07aa4154c0109b08ed68bd2e11a
Author: athrxx (athrxx at scummvm.org)
Date: 2026-08-25T22:51:35+02:00

Commit Message:
KYRA: (EOB automap feature) - remove some code

(some stuff that was added after the initial pr, which really doesn't
move into the right direction)

Changed paths:
    engines/kyra/engine/eobcommon.cpp
    engines/kyra/engine/eobcommon.h
    engines/kyra/engine/scene_eob.cpp
    engines/kyra/gui/gui_eob.cpp
    engines/kyra/gui/saveload_eob.cpp


diff --git a/engines/kyra/engine/eobcommon.cpp b/engines/kyra/engine/eobcommon.cpp
index dee83b48b2d..13a20a38c9c 100644
--- a/engines/kyra/engine/eobcommon.cpp
+++ b/engines/kyra/engine/eobcommon.cpp
@@ -113,11 +113,11 @@ EoBCoreEngine::EoBCoreEngine(OSystem *system, const GameFlags &flags) : KyraRpgE
 	_preventMonsterFlash = false;
 	_sceneShakeCountdown = 0;
 
-	automapReset();
-	_automapFullOpen = false;
-	_automapHudOn = false;
+	memset(_automapVisited, 0, sizeof(_automapVisited));
+	memset(_automapSeen, 0, sizeof(_automapSeen));
+	_automapVisible = false;
+	_automapSelectedBlock = 0xFFFF;
 	_automapEditing = false;
-	_automapNextRefresh = 0;
 
 	_teleporterPulse = 0;
 
@@ -384,10 +384,9 @@ Common::KeymapArray EoBCoreEngine::initKeymaps(const Common::String &gameId) {
 	addKeymapAction(keyMap, "TL", _("Turn left"), Common::KeyState(Common::KEYCODE_HOME), "HOME", "JOY_LEFT");
 	addKeymapAction(keyMap, "TR", _("Turn right"), Common::KeyState(Common::KEYCODE_PAGEUP), "PAGEUP", "JOY_RIGHT");
 
-	// Non-original: the automap overlay (toggle), its note editor and the mini-map HUD.
+	// Non-original: the automap overlay (toggle) and its note editor.
 	addKeymapAction(keyMap, "AMAP", _("Toggle automap"), Common::KeyState(Common::KEYCODE_TAB, '\t'), "TAB", "");
 	addKeymapAction(keyMap, "NOTE", _("Edit map note (while map open)"), Common::KeyState(Common::KEYCODE_n, 'n'), "n", "");
-	addKeymapAction(keyMap, "HUDM", _("Toggle mini-map (replaces the compass)"), Common::KeyState(Common::KEYCODE_m, 'm'), "m", "");
 	addKeymapAction(keyMap, "INV", _("Open / Close inventory"), Common::KeyState(Common::KEYCODE_i, 'i'), "i", "JOY_X");
 	addKeymapAction(keyMap, "SCE", _("Switch inventory / Character screen"), Common::KeyState(Common::KEYCODE_p, 'p'), "p", "JOY_Y");
 	addKeymapAction(keyMap, "CMP", _("Camp"), Common::KeyState(Common::KEYCODE_c, 'c'), "c", "");
@@ -725,10 +724,6 @@ void EoBCoreEngine::readSettings() {
 	_configEnhancedReload = ConfMan.getBool("mreload");
 	_configNPCPatch = (_flags.gameID == GI_EOB1) ? ConfMan.getBool("npcpatch") : false;
 	_configAutomap = ConfMan.getBool("automap");
-	// The mini-map HUD state survives restarts; SegaCD renders its play field
-	// through its own tile pipeline, so the HUD is not available there.
-	_automapHudOn = _configAutomap && _flags.platform != Common::kPlatformSegaCD &&
-		ConfMan.hasKey("automap_hud") && ConfMan.getBool("automap_hud");
 	_configSounds = ConfMan.getBool("sfx_mute") ? 0 : 1;
 	_configMusic = (_flags.platform == Common::kPlatformPC98 || _flags.platform == Common::kPlatformSegaCD) ? (ConfMan.getBool("music_mute") ? 0 : 1) : (_configSounds ? 1 : 0);
 
@@ -764,9 +759,6 @@ void EoBCoreEngine::writeSettings() {
 }
 
 void EoBCoreEngine::startupNew() {
-	// A new game must not inherit automap data, in particular from the EOB1 save
-	// that a party transfer imports via loadGameState(-1).
-	automapReset();
 	gui_setPlayFieldButtons();
 	_screen->_curPage = 0;
 	gui_drawPlayField(false);
@@ -788,41 +780,37 @@ void EoBCoreEngine::runLoop() {
 	while (!shouldQuit() && _runFlag) {
 		uint32 frameEnd = _system->getMillis() + 8;
 		checkPartyStatus(true);
-		// While the full map is up, suppress the play-field buttons so clicks hit the
-		// map. The mini-map HUD is non-modal: the game plays on unchanged beneath it.
-		const bool mapFull = _automapFullOpen;
-		int inputFlag = checkInput(mapFull ? 0 : _activeButtons, true, 0);
+		// While the map is up, suppress the play-field buttons so clicks hit the map.
+		int inputFlag = checkInput(_automapVisible ? 0 : _activeButtons, true, 0);
 		removeInputTop();
 
 		if (inputFlag && inputFlag == _keyMap[Common::KEYCODE_TAB]) {
 			if (_configAutomap)         // non-original automap, opt-out via game options
 				automapToggle();
-		} else if (inputFlag && inputFlag == _keyMap[Common::KEYCODE_m] && !mapFull) {
-			if (_configAutomap)
-				automapToggleHud();     // non-original: mini-map in the compass slot
-		} else if (mapFull && inputFlag && inputFlag == _keyMap[Common::KEYCODE_ESCAPE]) {
-			automapToggle();            // Esc closes the map (only while it is open)
-		} else if (mapFull && inputFlag == 199) {
+		} else if (_automapVisible && inputFlag && inputFlag == _keyMap[Common::KEYCODE_ESCAPE]) {
+			automapToggle();            // Esc also closes the map (only while it is open)
+		} else if (_automapVisible && inputFlag == 199) {
 			automapHandleClick();
-		} else if (mapFull && inputFlag && inputFlag == _keyMap[Common::KEYCODE_n]) {
+		} else if (_automapVisible && inputFlag && inputFlag == _keyMap[Common::KEYCODE_n]) {
 			automapEditNote();
-		} else if (mapFull && inputFlag && inputFlag == _keyMap[Common::KEYCODE_SPACE]) {
-			// Space jumps the selection back to the party.
-			_automapSelectedBlock = _currentBlock;
-		// Shift+arrows move the map selection cursor (place notes without a mouse);
-		// the 0x100 tag makes each value non-zero, so no separate inputFlag guard.
-		} else if (mapFull && inputFlag == (_keyMap[Common::KEYCODE_UP] | 0x100)) {
-			automapMoveSelection(0, -1);
-		} else if (mapFull && inputFlag == (_keyMap[Common::KEYCODE_DOWN] | 0x100)) {
-			automapMoveSelection(0, 1);
-		} else if (mapFull && inputFlag == (_keyMap[Common::KEYCODE_LEFT] | 0x100)) {
-			automapMoveSelection(-1, 0);
-		} else if (mapFull && inputFlag == (_keyMap[Common::KEYCODE_RIGHT] | 0x100)) {
-			automapMoveSelection(1, 0);
-		} else if (mapFull && inputFlag) {
+		} else if (_automapVisible && inputFlag &&
+		           (inputFlag == (_keyMap[Common::KEYCODE_UP] | 0x100) || inputFlag == (_keyMap[Common::KEYCODE_DOWN] | 0x100) ||
+		            inputFlag == (_keyMap[Common::KEYCODE_LEFT] | 0x100) || inputFlag == (_keyMap[Common::KEYCODE_RIGHT] | 0x100))) {
+			// Shift+arrows move the map selection cursor (place notes without a mouse).
+			if (inputFlag == (_keyMap[Common::KEYCODE_UP] | 0x100))
+				automapMoveSelection(0, -1);
+			else if (inputFlag == (_keyMap[Common::KEYCODE_DOWN] | 0x100))
+				automapMoveSelection(0, 1);
+			else if (inputFlag == (_keyMap[Common::KEYCODE_LEFT] | 0x100))
+				automapMoveSelection(-1, 0);
+			else
+				automapMoveSelection(1, 0);
+		} else if (_automapVisible && inputFlag &&
+		           (inputFlag == _keyMap[Common::KEYCODE_UP] || inputFlag == _keyMap[Common::KEYCODE_DOWN] ||
+		            inputFlag == _keyMap[Common::KEYCODE_LEFT] || inputFlag == _keyMap[Common::KEYCODE_RIGHT] ||
+		            inputFlag == _keyMap[Common::KEYCODE_HOME] || inputFlag == _keyMap[Common::KEYCODE_PAGEUP])) {
 			// Movement is normally button-driven, but those are suppressed now, so
-			// dispatch the arrow/turn keys here (the handlers only read button->index,
-			// so a dummy is fine); any other key falls through untouched.
+			// dispatch the keys here. The handlers only read button->index, so a dummy is fine.
 			Button dummy;
 			if (inputFlag == _keyMap[Common::KEYCODE_UP])
 				clickedUpArrow(&dummy);
@@ -834,7 +822,7 @@ void EoBCoreEngine::runLoop() {
 				clickedRightArrow(&dummy);
 			else if (inputFlag == _keyMap[Common::KEYCODE_HOME])
 				clickedTurnLeftArrow(&dummy);
-			else if (inputFlag == _keyMap[Common::KEYCODE_PAGEUP])
+			else
 				clickedTurnRightArrow(&dummy);
 		}
 
@@ -850,21 +838,10 @@ void EoBCoreEngine::runLoop() {
 		if (sceneRedraw)
 			automapMarkSeenFromCurrent();
 
-		if (_automapFullOpen) {
+		if (_automapVisible) {
 			// Opaque overlay: skip the dungeon redraw behind it (redrawn on close).
 			_sceneUpdateRequired = false;
-			// Redraw on activity only, plus a slow heartbeat so passive changes
-			// (live floor items, an overlay resize) still show up.
-			bool mapRedraw = inputFlag || sceneRedraw || _system->getMillis() >= _automapNextRefresh;
-			const uint16 hover = automapBlockAtMouse();
-			if (hover != _automapHoverBlock) {
-				_automapHoverBlock = hover; // hovered cell's info shows in the footer
-				mapRedraw = true;
-			}
-			if (mapRedraw) {
-				automapDraw();
-				_automapNextRefresh = _system->getMillis() + 250;
-			}
+			automapDraw();
 		} else if (sceneRedraw) {
 			drawScene(1);
 		}
diff --git a/engines/kyra/engine/eobcommon.h b/engines/kyra/engine/eobcommon.h
index df89529df5b..bcaa90375f9 100644
--- a/engines/kyra/engine/eobcommon.h
+++ b/engines/kyra/engine/eobcommon.h
@@ -665,22 +665,9 @@ protected:
 	bool automapIsVisited(uint16 block) const { return automapGetBit(_automapVisited, block); }
 	void automapMarkSeen(uint16 block) { automapSetBit(_automapSeen, block); }
 	bool automapIsSeen(uint16 block) const { return automapGetBit(_automapSeen, block); }
-	// True when the party can step off `block` in direction `d` (the neighbour's
-	// facing wall, dir^2, is passable) - the wall/movement test the map draws with.
-	bool automapSideOpen(uint16 block, int d) {
-		const uint16 nb = calcNewBlockPosition(block, d);
-		return (_wllWallFlags[_levelBlockProperties[nb].walls[d ^ 2]] & 1) != 0;
-	}
-	// A door leaf sits on both walls of its passage axis, so test either wall of the
-	// pair (N/S = walls 0,2; E/W = walls 1,3). Live state only - open doors animate.
-	bool automapDoorNS(const LevelBlockProperty *bp) const { return (_wllWallFlags[bp->walls[0]] & 8) || (_wllWallFlags[bp->walls[2]] & 8); }
-	bool automapDoorEW(const LevelBlockProperty *bp) const { return (_wllWallFlags[bp->walls[1]] & 8) || (_wllWallFlags[bp->walls[3]] & 8); }
 	void automapMarkSeenFromCurrent();
-	void automapReset();
-	void automapToggle();    // Tab / Esc: fullscreen map overlay
-	void automapToggleHud(); // M: mini-map in place of the compass (persisted in ConfMan)
+	void automapToggle();
 	void automapDraw();
-	void automapDrawMini();
 
 	// Geometry shared by drawing and click hit-testing.
 	struct AutomapLayout {
@@ -697,7 +684,6 @@ protected:
 	Common::String automapLevelName() const;
 	uint32 automapNoteKey(uint16 block) const { return ((uint32)_currentLevel << 16) | block; }
 	// Per-cell map notes (non-original): free text, keyed per level. Click selects, N edits.
-	uint16 automapBlockAtMouse() const;
 	void automapHandleClick();
 	void automapEditNote();
 
@@ -706,7 +692,6 @@ protected:
 	enum AutomapIcon { kAmNone = 0, kAmStairsDown = 1, kAmStairsUp = 2, kAmTeleport = 3 };
 	void automapCollectCellInfo(uint16 block);
 	void automapTagTransition(int fromLevel, uint16 fromBlock, int toLevel);
-	void automapLinkTeleport(uint16 fromBlock, uint16 toBlock);
 	Common::String automapLiveItems(uint16 block) const;
 	void automapDrawIcon(Graphics::Surface &surf, int sx, int sy, int cell, uint8 icon, uint32 color) const;
 	// Cached static chrome, rebuilt only on overlay resize; _automapFrame is the
@@ -716,16 +701,11 @@ protected:
 
 	uint8 _automapVisited[20][128];
 	uint8 _automapSeen[20][128];
-	bool _automapFullOpen; // fullscreen map overlay shown (modal)
-	bool _automapHudOn;    // mini-map drawn in the compass slot (non-modal)
+	bool _automapVisible;
 	Common::HashMap<uint32, Common::String> _automapNotes;
 	Common::HashMap<uint32, uint8> _automapIcons;
 	Common::HashMap<uint32, Common::String> _automapAutoInfo;
-	// Teleporter pads paired by use (same letter on both ends), keyed like the notes.
-	Common::HashMap<uint32, uint8> _automapTeleLinks;
 	uint16 _automapSelectedBlock;
-	uint16 _automapHoverBlock; // cell under the mouse (transient); its info shows in the footer
-	uint32 _automapNextRefresh; // next passive redraw (live floor items etc.); input redraws at once
 	bool _automapEditing;
 	Common::String _automapEditBuffer;
 	// Transient per-cell door bits (1 = N/S leaf, 2 = E/W), so an open door (which
diff --git a/engines/kyra/engine/scene_eob.cpp b/engines/kyra/engine/scene_eob.cpp
index d18305918ef..a2251d8e645 100644
--- a/engines/kyra/engine/scene_eob.cpp
+++ b/engines/kyra/engine/scene_eob.cpp
@@ -769,8 +769,6 @@ void EoBCoreEngine::moveParty(uint16 block) {
 
 	if (_currentLevel != preLevel)
 		automapTagTransition(preLevel, trigBlock, _currentLevel);
-	else if (_currentBlock != trigBlock)
-		automapLinkTeleport(trigBlock, _currentBlock); // a script moved us within the level
 	else
 		automapCollectCellInfo(block);
 
diff --git a/engines/kyra/gui/gui_eob.cpp b/engines/kyra/gui/gui_eob.cpp
index 98aac32ca20..ffc1aec4303 100644
--- a/engines/kyra/gui/gui_eob.cpp
+++ b/engines/kyra/gui/gui_eob.cpp
@@ -30,7 +30,6 @@
 #include "kyra/graphics/screen_eob_segacd.h"
 
 #include "backends/keymapper/keymapper.h"
-#include "common/config-manager.h"
 #include "common/system.h"
 #include "common/savefile.h"
 #include "graphics/scaler.h"
@@ -647,15 +646,6 @@ void EoBCoreEngine::gui_drawCharacterStatsPage() {
 }
 
 void EoBCoreEngine::gui_drawCompass(bool force) {
-	// Non-original: with the mini-map HUD on, the map takes the compass' place
-	// (it is north-up with a party arrow, so it carries the same information).
-	// Every path that would repaint the compass repaints the map instead.
-	if (_automapHudOn && _configAutomap && _flags.platform != Common::kPlatformSegaCD) {
-		automapDrawMini();
-		_compassDirection = _currentDirection;
-		return;
-	}
-
 	if (_currentDirection == _compassDirection && !force)
 		return;
 
@@ -4987,19 +4977,6 @@ const uint8 GUI_EoB::_highlightColorTableSegaCD[] = { 0x3D, 0x3D, 0x3D, 0x3E, 0x
 // Automap (non-original): a north-up map of the 32x32 level grid (block = (y<<5)|x),
 // drawn over the 3D view and toggled with Tab.
 
-// Forget all collected automap data (new game, party transfer, before a load).
-void EoBCoreEngine::automapReset() {
-	memset(_automapVisited, 0, sizeof(_automapVisited));
-	memset(_automapSeen, 0, sizeof(_automapSeen));
-	_automapNotes.clear();
-	_automapIcons.clear();
-	_automapAutoInfo.clear();
-	_automapTeleLinks.clear();
-	_automapDoorBits.clear();
-	_automapSelectedBlock = 0xFFFF;
-	_automapHoverBlock = 0xFFFF;
-}
-
 void EoBCoreEngine::automapMarkSeenFromCurrent() {
 	// Mark the cells in the line of sight ahead (not side cells), up to the first wall.
 	const int dir = _currentDirection;
@@ -5078,37 +5055,6 @@ void EoBCoreEngine::automapTagTransition(int fromLevel, uint16 fromBlock, int to
 	_automapAutoInfo[k] = info;
 }
 
-// A script moved the party within the level: pair the source and destination pads
-// with a shared letter (learned only by actually using the teleporter).
-void EoBCoreEngine::automapLinkTeleport(uint16 fromBlock, uint16 toBlock) {
-	if (_currentLevel >= 20 || fromBlock >= 1024 || toBlock >= 1024 || fromBlock == toBlock)
-		return;
-	const uint32 kf = automapNoteKey(fromBlock);
-	const uint32 kt = automapNoteKey(toBlock);
-	int id = -1;
-	if (_automapTeleLinks.contains(kf))
-		id = _automapTeleLinks[kf];
-	else if (_automapTeleLinks.contains(kt))
-		id = _automapTeleLinks[kt];
-	if (id < 0) {
-		// First unused pair letter on this level.
-		for (Common::HashMap<uint32, uint8>::const_iterator it = _automapTeleLinks.begin(); it != _automapTeleLinks.end(); ++it) {
-			if ((int)(it->_key >> 16) == _currentLevel)
-				id = MAX<int>(id, it->_value);
-		}
-		if (++id > 25) // out of letters: keep the plain glyph
-			return;
-	}
-	_automapTeleLinks[kf] = _automapTeleLinks[kt] = (uint8)id;
-	if (!_automapIcons.contains(kf))
-		_automapIcons[kf] = kAmTeleport;
-	if (!_automapIcons.contains(kt))
-		_automapIcons[kt] = kAmTeleport;
-	const Common::String info = Common::String::format("Teleport %c", 'A' + id);
-	_automapAutoInfo[kf] = info;
-	_automapAutoInfo[kt] = info;
-}
-
 void EoBCoreEngine::automapDrawIcon(Graphics::Surface &surf, int sx, int sy, int cell, uint8 icon, uint32 color) const {
 	const int cx = sx + cell / 2;
 	const int cy = sy + cell / 2;
@@ -5181,36 +5127,15 @@ static void automapDrawBigString(Graphics::Surface &dst, const Graphics::Font *f
 }
 
 void EoBCoreEngine::automapToggle() {
-	_automapFullOpen = !_automapFullOpen;
-	if (_automapFullOpen) {
+	_automapVisible = !_automapVisible;
+	if (_automapVisible) {
 		_automapSelectedBlock = _currentBlock;
-		_automapHoverBlock = 0xFFFF;
 		// inGUI=true => getMousePos() is in overlay coords, matching the drawn grid.
 		_system->showOverlay(true);
 	} else {
 		_system->hideOverlay();
-		_sceneUpdateRequired = true; // repaint the dungeon the overlay hid
-	}
-}
-
-// M: mini-map in the compass slot (the map is north-up with a party arrow, so it
-// covers the compass' job). The state is a config setting, so it survives restarts.
-void EoBCoreEngine::automapToggleHud() {
-	if (_flags.platform == Common::kPlatformSegaCD)
-		return; // the SegaCD play field is rendered through its own tile pipeline
-	_automapHudOn = !_automapHudOn;
-	ConfMan.setBool("automap_hud", _automapHudOn);
-	if (_automapHudOn) {
-		const int cp = _screen->setCurPage(0);
-		gui_drawCompass(true); // the compass hook draws the mini-map now
-		_screen->setCurPage(cp);
-	} else {
-		// Restore the play-field bitmap (and thus the compass) under the mini-map.
-		gui_drawPlayField(false);
-		gui_drawAllCharPortraitsWithStats();
 		_sceneUpdateRequired = true;
 	}
-	_screen->updateScreen();
 }
 
 // Solid triangle by scanlines (party arrow).
@@ -5304,36 +5229,26 @@ Common::String EoBCoreEngine::automapLevelName() const {
 	return Common::String::format("Level %d", _currentLevel);
 }
 
-// Move the selection cursor (Shift+arrows), clamped to the grid. Like the mouse
-// path, the cursor only lands on discovered cells - otherwise the selection frame
-// would sit on empty parchment where N silently does nothing.
+// Move the selection cursor (Shift+arrows), clamped to the grid.
 void EoBCoreEngine::automapMoveSelection(int dx, int dy) {
 	const uint16 cur = (_automapSelectedBlock != 0xFFFF) ? _automapSelectedBlock : _currentBlock;
 	const int bx = CLIP<int>((cur & 0x1F) + dx, 0, 31);
 	const int by = CLIP<int>((cur >> 5) + dy, 0, 31);
-	const uint16 dest = (by << 5) | bx;
-	if (automapIsVisited(dest) || automapIsSeen(dest))
-		_automapSelectedBlock = dest;
+	_automapSelectedBlock = (by << 5) | bx;
 }
 
-// Discovered grid cell under the mouse, 0xFFFF when outside or still unexplored.
-uint16 EoBCoreEngine::automapBlockAtMouse() const {
+// A click selects the cell (editing is a separate step, N), so the map can be browsed.
+void EoBCoreEngine::automapHandleClick() {
 	const Common::Point p = _eventMan->getMousePos();
 	const AutomapLayout l = automapLayout();
 	if (p.x < l.offX || p.y < l.offY)
-		return 0xFFFF;
+		return;
 	const int bx = (p.x - l.offX) / l.cell;
 	const int by = (p.y - l.offY) / l.cell;
-	if (bx >= 32 || by >= 32)
-		return 0xFFFF;
+	if (bx < 0 || bx >= 32 || by < 0 || by >= 32)
+		return;
 	const uint16 block = (by << 5) | bx;
-	return (automapIsVisited(block) || automapIsSeen(block)) ? block : 0xFFFF;
-}
-
-// A click selects the cell (editing is a separate step, N), so the map can be browsed.
-void EoBCoreEngine::automapHandleClick() {
-	const uint16 block = automapBlockAtMouse();
-	if (block != 0xFFFF)
+	if (automapIsVisited(block) || automapIsSeen(block))
 		_automapSelectedBlock = block;
 }
 
@@ -5354,21 +5269,12 @@ void EoBCoreEngine::automapEditNote() {
 
 	const uint kMaxLen = 40;
 	bool done = false, cancel = false;
-	bool redraw = true;
-	uint32 caretPhase = 0xFFFFFFFF;
 	while (!done && !shouldQuit()) {
-		// Redraw only after a keystroke or when the blinking caret flips phase.
-		const uint32 phase = (_system->getMillis() / 400) & 1;
-		if (redraw || phase != caretPhase) {
-			automapDraw();
-			caretPhase = phase;
-			redraw = false;
-		}
+		automapDraw();
 		updateInput();
 		for (Common::List<KyraEngine_v1::Event>::const_iterator e = _eventList.begin(); e != _eventList.end(); ++e) {
 			if (e->event.type != Common::EVENT_KEYDOWN)
 				continue;
-			redraw = true;
 			const Common::KeyCode kc = e->event.kbd.keycode;
 			const uint16 asc = e->event.kbd.ascii;
 			if (kc == Common::KEYCODE_RETURN || kc == Common::KEYCODE_KP_ENTER)
@@ -5474,10 +5380,10 @@ void EoBCoreEngine::automapDraw() {
 	const uint32 cTele      = fmt.RGBToColor(0x5a, 0x3f, 0x8a);
 	const uint32 cLever     = fmt.RGBToColor(0x9a, 0x2d, 0x2d);
 	const uint32 cNiche     = fmt.RGBToColor(0x8a, 0x5a, 0x1c);
-	const uint32 cNote      = fmt.RGBToColor(0x2f, 0x4d, 0x8f); // "pen ink" blue, distinct from the brown marks
+	const uint32 cNote      = fmt.RGBToColor(0x9a, 0x6a, 0x14);
 	const uint32 cParty     = fmt.RGBToColor(0xa8, 0x28, 0x1c);
 	const uint32 cPartyEdge = fmt.RGBToColor(0x5a, 0x14, 0x0e);
-	const uint32 cSel       = fmt.RGBToColor(0xe8, 0xb8, 0x30); // gold frame (per mockup), not another red
+	const uint32 cSel       = fmt.RGBToColor(0x7a, 0x2a, 0x18);
 	const uint32 cPlaqueBg  = fmt.RGBToColor(0x1c, 0x1a, 0x16);
 	const uint32 cPlaqueEd  = fmt.RGBToColor(0x0d, 0x0c, 0x0a);
 	const uint32 cGold      = fmt.RGBToColor(0xf0, 0xc8, 0x50);
@@ -5489,9 +5395,9 @@ void EoBCoreEngine::automapDraw() {
 	const int sc = CLIP<int>(oh / 320, 1, 3); // base side-panel text scale
 	const int mpad = MAX(8, L.mapW / 40);
 
-	// (Re)build the cached chrome on an overlay resize or pixel-format change
-	// (free() zeroes w/h, so an empty surface never matches).
-	if (_automapBg.w != ow || _automapBg.h != oh || _automapBg.format != fmt) {
+	// (Re)build the cached chrome on an overlay resize (free() zeroes w/h, so an
+	// empty surface never matches).
+	if (_automapBg.w != ow || _automapBg.h != oh) {
 		_automapBg.free();
 		_automapBg.create(ow, oh, fmt);
 		_automapFrame.free();
@@ -5536,95 +5442,44 @@ void EoBCoreEngine::automapDraw() {
 		bg.hLine(lx, cyy + 1, lx + colW - 1, cStoneEdge);
 		cyy += MAX(6, sc * 4);
 
-		// Legend rows are drawn with the same primitives as the map cells, on mini
-		// paper swatches, so what the player compares against matches the map. The
-		// double-slot rows show both variants (stairs up/down, pad/paired, lever/niche).
-		static const char *const legendLbl[9] = {
-			"Explored", "Glimpsed", "Party", "Stairs", "Teleporter", "Door", "Lever / Niche", "Loot", "Note"
+		static const char *const legendLbl[8] = {
+			"Party", "Stairs up", "Stairs down", "Teleporter", "Door", "Lever", "Niche", "Note"
 		};
-		const int isz = MAX(8, fh * sc);
-		const int slotGap = MAX(2, sc);
-		const int wt2 = MAX(1, isz / 5); // mini wall thickness
-		const int panelBottom = L.sideY + L.sideH - MAX(6, L.sideW / 12);
-		for (int i = 0; i < 9; ++i) {
-			if (cyy + rowH > panelBottom)
-				break; // tiny window: drop the tail rather than draw past the panel
-			const int iy = cyy;
-			const int slots = (i == 3 || i == 4 || i == 6) ? 2 : 1;
-			for (int s = 0; s < slots; ++s) {
-				const int ix = lx + s * (isz + slotGap);
-				bg.fillRect(Common::Rect(ix, iy, ix + isz, iy + isz), (i == 1) ? cFloorSeen : cFloor);
-				switch (i) {
-				case 0: // explored: grid corner like a visited cell
-					bg.hLine(ix, iy, ix + isz - 1, cGrid);
-					bg.vLine(ix, iy, iy + isz - 1, cGrid);
-					break;
-				case 1: // glimpsed: paler floor, dim wall
-					bg.fillRect(Common::Rect(ix, iy, ix + isz, iy + wt2), cWallSeen);
-					break;
-				case 2:
-					automapFillTri(bg, ix + isz / 2, iy + 1, ix + isz - 1, iy + isz - 1, ix + 1, iy + isz - 1, cParty);
-					break;
-				case 3: // stairs up / down
-					if (s == 0)
-						automapFillTri(bg, ix + 1, iy + isz - 1, ix + isz - 1, iy + isz - 1, ix + isz / 2, iy + 1, cStair);
-					else
-						automapFillTri(bg, ix + 1, iy + 1, ix + isz - 1, iy + 1, ix + isz / 2, iy + isz - 1, cStair);
-					break;
-				case 4: // teleporter pad; used pads pair up and show a letter instead
-					if (s == 0) {
-						bg.frameRect(Common::Rect(ix + 1, iy + 1, ix + isz - 1, iy + isz - 1), cTele);
-						bg.frameRect(Common::Rect(ix + 2, iy + 2, ix + isz - 2, iy + isz - 2), cTele);
-					} else if (bigFont && fh <= isz) {
-						automapDrawBigString(bg, bigFont, "A", ix, iy + (isz - fh) / 2, isz, cTele, 1);
-					}
-					break;
-				case 5: // door leaf across the cell
-					bg.fillRect(Common::Rect(ix + 1, iy + (isz - wt2) / 2, ix + isz - 1, iy + (isz - wt2) / 2 + wt2 + 1), cDoor);
-					break;
-				case 6: { // lever / niche: pip on the carrying wall edge
-					const int ps = MAX(2, isz / 3);
-					bg.fillRect(Common::Rect(ix + (isz - ps) / 2, iy, ix + (isz + ps) / 2, iy + ps), (s == 0) ? cLever : cNiche);
-					break;
-				}
-				case 7: { // loot dot, bottom-left like the map
-					const int ls2 = MAX(2, isz / 3);
-					bg.fillRect(Common::Rect(ix + 1, iy + isz - ls2 - 1, ix + 1 + ls2, iy + isz - 1), cGold);
-					break;
-				}
-				default: { // note dot, top-right corner like the map
-					const int ds = MAX(2, isz / 3);
-					bg.fillRect(Common::Rect(ix + isz - ds, iy, ix + isz, iy + ds), cNote);
-					break;
-				}
-				}
+		const int isz = MAX(6, fh * sc);
+		for (int i = 0; i < 8; ++i) {
+			const int ix = lx, iy = cyy;
+			switch (i) {
+			case 0: automapFillTri(bg, ix + isz / 2, iy, ix + isz, iy + isz, ix, iy + isz, cParty); break;       // party (up)
+			case 1: automapFillTri(bg, ix, iy + isz, ix + isz, iy + isz, ix + isz / 2, iy, cStair); break;       // stairs up
+			case 2: automapFillTri(bg, ix, iy, ix + isz, iy, ix + isz / 2, iy + isz, cStair); break;             // stairs down
+			case 3: bg.frameRect(Common::Rect(ix, iy, ix + isz, iy + isz), cTele);
+			        bg.frameRect(Common::Rect(ix + 1, iy + 1, ix + isz - 1, iy + isz - 1), cTele); break;        // teleporter
+			case 4: bg.fillRect(Common::Rect(ix, iy + isz / 3, ix + isz, iy + isz - isz / 3), cDoor); break;     // door
+			case 5: bg.fillRect(Common::Rect(ix + isz / 3, iy, ix + isz - isz / 3, iy + isz), cLever); break;    // lever
+			case 6: bg.fillRect(Common::Rect(ix + isz / 3, iy, ix + isz - isz / 3, iy + isz), cNiche); break;    // niche
+			default: bg.fillRect(Common::Rect(ix, iy, ix + isz, iy + isz), cNote); break;                        // note
 			}
-			const int iconW = 2 * isz + slotGap; // fixed two-slot column keeps labels aligned
-			const int tx = lx + iconW + MAX(4, sc * 2);
-			const int tw = colW - iconW - MAX(4, sc * 2);
+			const int tx = lx + isz + MAX(4, sc * 2);
+			const int tw = colW - isz - MAX(4, sc * 2);
 			const int lsc = automapFit(bigFont, legendLbl[i], tw, sc);
 			automapDrawBigString(bg, bigFont, legendLbl[i], tx, iy + (isz - fh * lsc) / 2, tw, cPanelTxt, lsc, Graphics::kTextAlignLeft);
 			cyy += rowH;
 		}
 
-		// KEYS: a single N chip (the rest is hinted in the empty footer strip).
+		cyy += MAX(6, sc * 5);
+		automapDrawBigString(bg, bigFont, "- KEYS -", lx, cyy, colW, cGoldDim, sc);
+		cyy += fh * sc + MAX(4, sc * 3);
+		bg.hLine(lx, cyy, lx + colW - 1, cStoneHi);
+		bg.hLine(lx, cyy + 1, lx + colW - 1, cStoneEdge);
+		cyy += MAX(6, sc * 4);
 		const int chipPad = MAX(2, sc * 2);
+		const int chipW = (bigFont ? bigFont->getStringWidth("N") * sc : 6 * sc) + chipPad * 2;
 		const int chipH = fh * sc + chipPad;
-		const int keysH = MAX(6, sc * 5) + fh * sc + MAX(4, sc * 3) + 2 + MAX(6, sc * 4) + chipH;
-		if (cyy + keysH <= panelBottom) {
-			cyy += MAX(6, sc * 5);
-			automapDrawBigString(bg, bigFont, "- KEYS -", lx, cyy, colW, cGoldDim, sc);
-			cyy += fh * sc + MAX(4, sc * 3);
-			bg.hLine(lx, cyy, lx + colW - 1, cStoneHi);
-			bg.hLine(lx, cyy + 1, lx + colW - 1, cStoneEdge);
-			cyy += MAX(6, sc * 4);
-			const int chipW = (bigFont ? bigFont->getStringWidth("N") * sc : 6 * sc) + chipPad * 2;
-			bg.fillRect(Common::Rect(lx, cyy, lx + chipW, cyy + chipH), cPlaqueEd);
-			bg.fillRect(Common::Rect(lx + 1, cyy + 1, lx + chipW - 1, cyy + chipH - 1), cPlaqueBg);
-			automapDrawBigString(bg, bigFont, "N", lx, cyy + chipPad / 2, chipW, cGold, sc);
-			automapDrawBigString(bg, bigFont, "Add note", lx + chipW + MAX(4, sc * 3), cyy + (chipH - fh * sc) / 2,
-				colW - chipW - MAX(4, sc * 3), cPanelTxt, sc, Graphics::kTextAlignLeft);
-		}
+		bg.fillRect(Common::Rect(lx, cyy, lx + chipW, cyy + chipH), cPlaqueEd);
+		bg.fillRect(Common::Rect(lx + 1, cyy + 1, lx + chipW - 1, cyy + chipH - 1), cPlaqueBg);
+		automapDrawBigString(bg, bigFont, "N", lx, cyy + chipPad / 2, chipW, cGold, sc);
+		automapDrawBigString(bg, bigFont, "Add note", lx + chipW + MAX(4, sc * 3), cyy + (chipH - fh * sc) / 2,
+			colW - chipW - MAX(4, sc * 3), cPanelTxt, sc, Graphics::kTextAlignLeft);
 	}
 
 	// Per frame: copy the chrome into the reused surface, then draw dynamics on top.
@@ -5649,17 +5504,19 @@ void EoBCoreEngine::automapDraw() {
 
 			// A side is a wall only where the party can't step (neighbour's facing wall, dir^2).
 			bool wall[4];
-			for (int d = 0; d < 4; ++d)
-				wall[d] = !automapSideOpen(block, d);
+			for (int d = 0; d < 4; ++d) {
+				const uint16 nb = calcNewBlockPosition(block, d);
+				wall[d] = !(_wllWallFlags[_levelBlockProperties[nb].walls[d ^ 2]] & 1);
+			}
 
 			// Cache the door bits: an open door loses its wall flag mid-animation but
 			// should still draw as a door.
 			const LevelBlockProperty *bp = &_levelBlockProperties[block];
 			const uint32 bkey = automapNoteKey(block);
 			uint8 dbits = _automapDoorBits.contains(bkey) ? _automapDoorBits[bkey] : 0;
-			if (automapDoorNS(bp))
+			if ((_wllWallFlags[bp->walls[0]] & 8) || (_wllWallFlags[bp->walls[2]] & 8))
 				dbits |= 1;
-			if (automapDoorEW(bp))
+			if ((_wllWallFlags[bp->walls[1]] & 8) || (_wllWallFlags[bp->walls[3]] & 8))
 				dbits |= 2;
 			if (dbits)
 				_automapDoorBits[bkey] = dbits;
@@ -5708,31 +5565,18 @@ void EoBCoreEngine::automapDraw() {
 				surf.fillRect(Common::Rect(px, py, px + ps, py + ps), pcol);
 			}
 
-			// Paired teleporters show a matching letter on both ends (replacing the
-			// glyph); when the cell is too small for the font, keep the glyph.
-			const bool linkLetter = _automapTeleLinks.contains(bkey) && bigFont && fh + 2 <= cell;
-			if (_automapIcons.contains(bkey) && !linkLetter) {
+			if (_automapIcons.contains(bkey)) {
 				const uint8 ic = _automapIcons[bkey];
 				uint32 col = (ic == kAmTeleport) ? cTele : cStair;
 				if (!visited)
 					col = cWallSeen;
 				automapDrawIcon(surf, sx, sy, cell, ic, col);
 			}
-			if (linkLetter) {
-				automapDrawBigString(surf, bigFont, Common::String::format("%c", 'A' + (_automapTeleLinks[bkey] % 26)),
-					sx, sy + (cell - fh) / 2, cell, visited ? cTele : cWallSeen, 1);
-			}
 
 			if (_automapNotes.contains(bkey)) {
 				const int ds = MAX(2, cell / 3);
 				surf.fillRect(Common::Rect(sx + cell - ds, sy, sx + cell, sy + ds), cNote);
 			}
-
-			// Loot dot (bottom-left): items lying on this cell, read live.
-			if (bp->drawObjects && !automapLiveItems(block).empty()) {
-				const int ls = MAX(2, cell / 4);
-				surf.fillRect(Common::Rect(sx + 1, sy + cell - ls - 1, sx + 1 + ls, sy + cell - 1), cGold);
-			}
 		}
 	}
 
@@ -5765,8 +5609,7 @@ void EoBCoreEngine::automapDraw() {
 
 	// Plaque text: area name over coords, in two size-fitted bands so they never overlap.
 	if (bigFont) {
-		const uint16 cb = (_automapHoverBlock != 0xFFFF) ? _automapHoverBlock
-			: ((_automapSelectedBlock != 0xFFFF) ? _automapSelectedBlock : _currentBlock);
+		const uint16 cb = (_automapSelectedBlock != 0xFFFF) ? _automapSelectedBlock : _currentBlock;
 		const Common::String lvl = automapLevelName();
 		const Common::String crd = Common::String::format("X %d   Y %d", cb & 0x1F, cb >> 5);
 		const int pm = MAX(3, sc * 2);
@@ -5789,36 +5632,29 @@ void EoBCoreEngine::automapDraw() {
 		if (_automapEditing) {
 			const char *caret = ((_system->getMillis() / 400) & 1) ? "_" : " ";
 			foot = Common::String("Note: ") + _automapEditBuffer + caret;
-		} else {
-			// The hovered cell's info previews in the footer; otherwise the selected one's.
-			const uint16 ib = (_automapHoverBlock != 0xFFFF) ? _automapHoverBlock : _automapSelectedBlock;
-			if (ib != 0xFFFF) {
-				// A note must not hide what the map learned: show note, auto-info
-				// and floor items together.
-				const uint32 ik = automapNoteKey(ib);
-				if (_automapNotes.contains(ik))
-					foot = _automapNotes[ik];
-				if (_automapAutoInfo.contains(ik))
-					foot = foot.empty() ? _automapAutoInfo[ik] : foot + " - " + _automapAutoInfo[ik];
-				const Common::String items = automapLiveItems(ib);
+		} else if (_automapSelectedBlock != 0xFFFF) {
+			const uint32 selKey = automapNoteKey(_automapSelectedBlock);
+			Common::String body;
+			if (_automapNotes.contains(selKey)) {
+				body = _automapNotes[selKey];
+			} else {
+				if (_automapAutoInfo.contains(selKey))
+					body = _automapAutoInfo[selKey];
+				const Common::String items = automapLiveItems(_automapSelectedBlock);
 				if (!items.empty())
-					foot = foot.empty() ? items : foot + " - " + items;
+					body = body.empty() ? items : body + " - " + items;
 			}
+			if (!body.empty())
+				foot = Common::String("Selected: ") + body;
 		}
-		uint32 footCol = cInk;
-		if (foot.empty()) {
-			// Idle footer doubles as the key hint (keeps the side panel to just N).
-			foot = "Click a cell - N note - SPACE find party - M mini-map";
-			footCol = cInkSoft;
-		}
-		{
+		if (!foot.empty()) {
 			const int footH = (L.mapY + L.mapH) - L.footY;
 			const int fpad = MAX(2, footH / 6);
 			const int maxFsc = MAX(1, (footH - 2 * fpad) / fh);
 			const int fw = L.mapW - 2 * mpad;
 			const int fsc = MIN(automapFit(bigFont, foot, fw, sc), maxFsc);
 			const int fy = L.footY + (footH - fh * fsc) / 2;
-			automapDrawBigString(surf, bigFont, foot, L.mapX + mpad, fy, fw, footCol, fsc, Graphics::kTextAlignLeft);
+			automapDrawBigString(surf, bigFont, foot, L.mapX + mpad, fy, fw, cInk, fsc, Graphics::kTextAlignLeft);
 		}
 	}
 
@@ -5826,98 +5662,6 @@ void EoBCoreEngine::automapDraw() {
 	_system->updateScreen();
 }
 
-// Mini-map HUD (toggled with M): an 11x11 window around the party drawn over the
-// compass dome in palette colors while the game runs on. Reached only through
-// gui_drawCompass(), so every path that repaints the compass repaints the map.
-void EoBCoreEngine::automapDrawMini() {
-	if (_currentLevel < 1 || _currentLevel >= 20)
-		return;
-	automapMarkVisited(_currentBlock);
-
-	const KyraRpgGUISettings::Colors &c = guiSettings()->colors;
-	const uint8 colFloor = (c.fill >= 0) ? (uint8)c.fill : c.frame2;
-	const uint8 colSeen  = c.frame2;
-	const uint8 colWall  = c.guiColorWhite; // bright wall outlines keep the cell grid readable
-	const uint8 colDoor  = c.guiColorBrown;
-	const uint8 colParty = c.guiColorLightRed;
-	const uint8 colTick  = c.guiColorWhite;
-	const uint8 colStair = c.guiColorLightGreen;
-	const uint8 colTele  = c.guiColorLightBlue;
-	const uint8 colNote  = c.guiColorYellow;
-
-	// An opaque panel lined up with the surrounding chrome: from the movement-arrow
-	// block (ends at x=66, see the button defs) to the view port's right edge
-	// (x=175), and from the view port's bottom row down over the whole compass dome
-	// incl. the needle mount. Staying below y=120 keeps it clear of view-port blits.
-	const int cell = 4, radiusX = 12, radiusY = 5;
-	// The ZH_TWN text box starts at y=170, so don't reach under it there.
-	const int bw = 108, bh = (_flags.lang == Common::ZH_TWN) ? 50 : 56;
-	const int bx0 = 68;
-	const int by0 = 120;
-	const int x0 = bx0 + (bw - (2 * radiusX + 1) * cell) / 2;
-	const int y0 = by0 + (bh - (2 * radiusY + 1) * cell) / 2;
-	const int px = _currentBlock & 0x1F, py = _currentBlock >> 5;
-
-	// Draw to the current page, exactly like the compass shapes this replaces
-	// (gui_drawPlayField composes on page 2, the scene path draws on page 0).
-	const int pg = _screen->_curPage;
-	gui_drawBox(bx0, by0, bw, bh, c.frame1, c.frame2, c.guiColorBlack);
-
-	for (int dy = -radiusY; dy <= radiusY; ++dy) {
-		for (int dx = -radiusX; dx <= radiusX; ++dx) {
-			const int bx = px + dx, by = py + dy;
-			if (bx < 0 || bx > 31 || by < 0 || by > 31)
-				continue;
-			const uint16 block = (by << 5) | bx;
-			const bool visited = automapIsVisited(block);
-			if (!visited && !automapIsSeen(block))
-				continue;
-			const int sx = x0 + (dx + radiusX) * cell;
-			const int sy = y0 + (dy + radiusY) * cell;
-			_screen->fillRect(sx, sy, sx + cell - 1, sy + cell - 1, visited ? colFloor : colSeen, pg);
-
-			// Same wall/door tests as the full map, at 1px scale.
-			const LevelBlockProperty *bp = &_levelBlockProperties[block];
-			const bool doorNS = automapDoorNS(bp);
-			const bool doorEW = automapDoorEW(bp);
-			for (int d = 0; d < 4; ++d) {
-				if (((d == 0 || d == 2) && doorNS) || ((d == 1 || d == 3) && doorEW))
-					continue;
-				if (automapSideOpen(block, d))
-					continue;
-				switch (d) {
-				case 0:  _screen->fillRect(sx, sy, sx + cell - 1, sy, colWall, pg); break;
-				case 1:  _screen->fillRect(sx + cell - 1, sy, sx + cell - 1, sy + cell - 1, colWall, pg); break;
-				case 2:  _screen->fillRect(sx, sy + cell - 1, sx + cell - 1, sy + cell - 1, colWall, pg); break;
-				default: _screen->fillRect(sx, sy, sx, sy + cell - 1, colWall, pg); break;
-				}
-			}
-			if (doorNS)
-				_screen->fillRect(sx, sy + cell / 2, sx + cell - 1, sy + cell / 2, colDoor, pg);
-			if (doorEW)
-				_screen->fillRect(sx + cell / 2, sy, sx + cell / 2, sy + cell - 1, colDoor, pg);
-
-			const uint32 bkey = automapNoteKey(block);
-			if (_automapIcons.contains(bkey)) {
-				const uint8 icol = (_automapIcons[bkey] == kAmTeleport) ? colTele : colStair;
-				_screen->fillRect(sx + 1, sy + 1, sx + cell - 2, sy + cell - 2, icol, pg);
-			} else if (_automapNotes.contains(bkey)) {
-				_screen->fillRect(sx + 1, sy + 1, sx + cell - 2, sy + cell - 2, colNote, pg);
-			}
-		}
-	}
-
-	// Party cell, with a facing tick on the leading edge.
-	const int psx = x0 + radiusX * cell, psy = y0 + radiusY * cell;
-	_screen->fillRect(psx, psy, psx + cell - 1, psy + cell - 1, colParty, pg);
-	switch (_currentDirection) {
-	case 1:  _screen->fillRect(psx + cell - 1, psy + 1, psx + cell - 1, psy + cell - 2, colTick, pg); break;
-	case 2:  _screen->fillRect(psx + 1, psy + cell - 1, psx + cell - 2, psy + cell - 1, colTick, pg); break;
-	case 3:  _screen->fillRect(psx, psy + 1, psx, psy + cell - 2, colTick, pg); break;
-	default: _screen->fillRect(psx + 1, psy, psx + cell - 2, psy, colTick, pg); break;
-	}
-}
-
 } // End of namespace Kyra
 
 #endif // ENABLE_EOB
diff --git a/engines/kyra/gui/saveload_eob.cpp b/engines/kyra/gui/saveload_eob.cpp
index 4115aa577f1..ae79d6eab29 100644
--- a/engines/kyra/gui/saveload_eob.cpp
+++ b/engines/kyra/gui/saveload_eob.cpp
@@ -34,41 +34,12 @@
 
 namespace Kyra {
 
-// Bounds for the automap records read from a save. Real data stays far below both
-// (20 levels x 1024 blocks; notes are capped at 40 chars when typed), so hitting a
-// bound means a corrupt/foreign file - stop reading rather than hang or OOM.
-enum {
-	kAutomapMaxEntries = 20 * 1024,
-	kAutomapMaxStrLen = 256
-};
-
 // Automap key->string maps: a count, then key + null-terminated string per entry.
 static void automapReadStringMap(Common::ReadStream &in, Common::HashMap<uint32, Common::String> &map) {
 	uint32 n = in.readUint32BE();
-	if (in.eos() || n > kAutomapMaxEntries)
-		return;
-	for (uint32 i = 0; i < n && !in.eos(); ++i) {
+	for (uint32 i = 0; i < n; ++i) {
 		uint32 k = in.readUint32BE();
-		map[k] = in.readString(0, kAutomapMaxStrLen);
-	}
-}
-
-// Automap key->byte maps (glyph codes, teleporter pair ids): count, then key + byte.
-static void automapReadByteMap(Common::ReadStream &in, Common::HashMap<uint32, uint8> &map) {
-	uint32 n = in.readUint32BE();
-	if (in.eos() || n > kAutomapMaxEntries)
-		return;
-	for (uint32 i = 0; i < n && !in.eos(); ++i) {
-		uint32 k = in.readUint32BE();
-		map[k] = in.readByte();
-	}
-}
-
-static void automapWriteByteMap(Common::WriteStream *out, const Common::HashMap<uint32, uint8> &map) {
-	out->writeUint32BE(map.size());
-	for (Common::HashMap<uint32, uint8>::const_iterator it = map.begin(); it != map.end(); ++it) {
-		out->writeUint32BE(it->_key);
-		out->writeByte(it->_value);
+		map[k] = in.readString();
 	}
 }
 
@@ -329,14 +300,22 @@ Common::Error EoBCoreEngine::loadGameState(int slot) {
 
 	// Automap data (save version >= 25). Clear first (incl. the transient door-bit
 	// cache, which must not carry over) and only read when the save is new enough.
-	automapReset();
+	memset(_automapVisited, 0, sizeof(_automapVisited));
+	memset(_automapSeen, 0, sizeof(_automapSeen));
+	_automapNotes.clear();
+	_automapIcons.clear();
+	_automapAutoInfo.clear();
+	_automapDoorBits.clear();
 	if (header.version >= 25) {
 		in.read(_automapVisited, sizeof(_automapVisited));
 		in.read(_automapSeen, sizeof(_automapSeen));
 		automapReadStringMap(in, _automapNotes);
-		automapReadByteMap(in, _automapIcons);
+		uint32 numIcons = in.readUint32BE();
+		for (uint32 i = 0; i < numIcons; ++i) {
+			uint32 k = in.readUint32BE();
+			_automapIcons[k] = in.readByte();
+		}
 		automapReadStringMap(in, _automapAutoInfo);
-		automapReadByteMap(in, _automapTeleLinks);
 	}
 
 	loadLevel(_currentLevel, _currentSub);
@@ -597,15 +576,6 @@ Common::Error EoBCoreEngine::saveGameStateIntern(int slot, const char *saveName,
 		}
 	}
 
-	// Automap data (save version >= 25): visited/seen bitfields, notes, glyphs,
-	// info, teleporter pair ids.
-	out->write(_automapVisited, sizeof(_automapVisited));
-	out->write(_automapSeen, sizeof(_automapSeen));
-	automapWriteStringMap(out, _automapNotes);
-	automapWriteByteMap(out, _automapIcons);
-	automapWriteStringMap(out, _automapAutoInfo);
-	automapWriteByteMap(out, _automapTeleLinks);
-
 	out->finalize();
 
 	// check for errors


Commit: b4624adcd67a6b65afcdbc8b27db74cb2612e84b
    https://github.com/scummvm/scummvm/commit/b4624adcd67a6b65afcdbc8b27db74cb2612e84b
Author: athrxx (athrxx at scummvm.org)
Date: 2026-08-25T22:51:35+02:00

Commit Message:
KYRA: (EOB automap feature) - move code to new class and file

The current functionality remains intact with this commit. I have
tried to exercise self-control here in this first commit and mostly
just transfer the code.

Some exceptions:
- The code is very obviously AI-generated, or at least asssisted to
   an extent that makes it look AI-generated. I have gotten rid of many
   comments that are just there, because the AI wants to fry our brains.
- There is a change to the way the map explore data is stored (and
   thus to the save format), since this is data that we definitely need
   and we can store it with less overhead.
- There is a change to the exploration view. It now uncovers two
   extra blocks to the front left and front right, to have a more
   realistic view and to avoid the need of unnecessarily walking
   around just for map development.
- I have moved some strings to a separate table, since these should
  eventually be translated to the target's language (and maybe put
  into KYRA.DAT)

Changed paths:
  A engines/kyra/gui/automap_eob.cpp
  A engines/kyra/gui/automap_eob.h
    engines/kyra/detection.h
    engines/kyra/detection_tables.h
    engines/kyra/engine/eobcommon.cpp
    engines/kyra/engine/eobcommon.h
    engines/kyra/engine/kyra_rpg.h
    engines/kyra/engine/lol.h
    engines/kyra/engine/scene_eob.cpp
    engines/kyra/gui/gui_eob.cpp
    engines/kyra/gui/saveload_eob.cpp
    engines/kyra/gui/saveload_lol.cpp
    engines/kyra/gui/saveload_rpg.cpp
    engines/kyra/metaengine.cpp
    engines/kyra/module.mk


diff --git a/engines/kyra/detection.h b/engines/kyra/detection.h
index 75bd0e16f30..162234718a3 100644
--- a/engines/kyra/detection.h
+++ b/engines/kyra/detection.h
@@ -72,20 +72,21 @@ struct KYRAGameDescription {
 	Kyra::GameFlags flags;
 };
 
-#define GAMEOPTION_KYRA3_AUDIENCE GUIO_GAMEOPTIONS1
-#define GAMEOPTION_KYRA3_SKIP     GUIO_GAMEOPTIONS2
-#define GAMEOPTION_KYRA3_HELIUM   GUIO_GAMEOPTIONS3
-
-#define GAMEOPTION_LOL_SCROLLING  GUIO_GAMEOPTIONS4
-#define GAMEOPTION_LOL_CURSORS    GUIO_GAMEOPTIONS5
-#define GAMEOPTION_LOL_SAVENAMES  GUIO_GAMEOPTIONS8
-
-#define GAMEOPTION_EOB_HPGRAPHS   GUIO_GAMEOPTIONS6
-#define GAMEOPTION_EOB_MOUSESWAP  GUIO_GAMEOPTIONS7
-#define GAMEOPTION_EOB_ADDRULES   GUIO_GAMEOPTIONS9
-#define GAMEOPTION_EOB_RELOAD	  GUIO_GAMEOPTIONS10
-#define GAMEOPTION_EOB_NPCPATCH   GUIO_GAMEOPTIONS11
-#define GAMEOPTION_EOB_AUTOMAP    GUIO_GAMEOPTIONS12
+#define GAMEOPTION_KYRA3_AUDIENCE		GUIO_GAMEOPTIONS1
+#define GAMEOPTION_KYRA3_SKIP			GUIO_GAMEOPTIONS2
+#define GAMEOPTION_KYRA3_HELIUM			GUIO_GAMEOPTIONS3
+
+#define GAMEOPTION_LOL_SCROLLING		GUIO_GAMEOPTIONS4
+#define GAMEOPTION_LOL_CURSORS			GUIO_GAMEOPTIONS5
+#define GAMEOPTION_LOL_SAVENAMES		GUIO_GAMEOPTIONS8
+
+#define GAMEOPTION_EOB_HPGRAPHS			GUIO_GAMEOPTIONS6
+#define GAMEOPTION_EOB_MOUSESWAP		GUIO_GAMEOPTIONS7
+#define GAMEOPTION_EOB_ADDRULES			GUIO_GAMEOPTIONS9
+#define GAMEOPTION_EOB_RELOAD			GUIO_GAMEOPTIONS10
+#define GAMEOPTION_EOB_NPCPATCH			GUIO_GAMEOPTIONS11
+#define GAMEOPTION_EOB_AUTOMAP			GUIO_GAMEOPTIONS12
+#define GAMEOPTION_EOB_AUTOMAP_SEGA		GUIO_GAMEOPTIONS13
 
 } // End of anonymous namespace
 
diff --git a/engines/kyra/detection_tables.h b/engines/kyra/detection_tables.h
index 65c8cbc28e6..8c52223a154 100644
--- a/engines/kyra/detection_tables.h
+++ b/engines/kyra/detection_tables.h
@@ -77,7 +77,7 @@ namespace {
 #define GUIO_EOB1_DOS        (GUIO_NOSPEECH GUIO_MIDIADLIB GUIO_MIDIPCSPK GUIO_MIDIPCJR GUIO_RENDERVGA GUIO_RENDEREGA GUIO_RENDERCGA GAMEOPTION_EOB_HPGRAPHS GAMEOPTION_EOB_MOUSESWAP GAMEOPTION_EOB_ADDRULES GAMEOPTION_EOB_RELOAD GAMEOPTION_EOB_NPCPATCH GAMEOPTION_EOB_AUTOMAP)
 #define GUIO_EOB1_AMIGA      (GUIO_NOSPEECH GUIO_MIDIAMIGA GUIO_RENDERAMIGA GAMEOPTION_EOB_HPGRAPHS GAMEOPTION_EOB_MOUSESWAP GAMEOPTION_EOB_ADDRULES GAMEOPTION_EOB_RELOAD GAMEOPTION_EOB_NPCPATCH GAMEOPTION_EOB_AUTOMAP)
 #define GUIO_EOB1_PC98       (GUIO_NOSPEECH GUIO_MIDIPC98 GUIO_RENDERPC98_16C GAMEOPTION_EOB_HPGRAPHS GAMEOPTION_EOB_MOUSESWAP GAMEOPTION_EOB_ADDRULES GAMEOPTION_EOB_RELOAD GAMEOPTION_EOB_NPCPATCH GAMEOPTION_EOB_AUTOMAP)
-#define GUIO_EOB1_SEGACD     (GUIO_NOSPEECH GUIO_MIDISEGACD GAMEOPTION_EOB_MOUSESWAP GAMEOPTION_EOB_ADDRULES GAMEOPTION_EOB_RELOAD GAMEOPTION_EOB_NPCPATCH GAMEOPTION_EOB_AUTOMAP)
+#define GUIO_EOB1_SEGACD     (GUIO_NOSPEECH GUIO_MIDISEGACD GAMEOPTION_EOB_MOUSESWAP GAMEOPTION_EOB_ADDRULES GAMEOPTION_EOB_RELOAD GAMEOPTION_EOB_NPCPATCH GAMEOPTION_EOB_AUTOMAP_SEGA)
 #define GUIO_EOB2_DOS        (GUIO_NOSPEECH GUIO_MIDIADLIB GUIO_MIDIPCSPK GUIO_RENDERVGA GUIO_RENDEREGA GAMEOPTION_EOB_HPGRAPHS GAMEOPTION_EOB_MOUSESWAP GAMEOPTION_EOB_ADDRULES GAMEOPTION_EOB_RELOAD GAMEOPTION_EOB_AUTOMAP)
 #define GUIO_EOB2_DOS_TALKIE (GUIO_MIDIADLIB GUIO_MIDIPCSPK GUIO_RENDERVGA GUIO_RENDEREGA GAMEOPTION_EOB_HPGRAPHS GAMEOPTION_EOB_MOUSESWAP GAMEOPTION_EOB_ADDRULES GAMEOPTION_EOB_RELOAD GAMEOPTION_EOB_AUTOMAP)
 #define GUIO_EOB2_FMTOWNS    (GUIO_NOSPEECH GUIO_MIDITOWNS GUIO_RENDERFMTOWNS GAMEOPTION_EOB_HPGRAPHS GAMEOPTION_EOB_MOUSESWAP GAMEOPTION_EOB_ADDRULES GAMEOPTION_EOB_RELOAD GAMEOPTION_EOB_AUTOMAP)
diff --git a/engines/kyra/engine/eobcommon.cpp b/engines/kyra/engine/eobcommon.cpp
index 13a20a38c9c..bece9fac13f 100644
--- a/engines/kyra/engine/eobcommon.cpp
+++ b/engines/kyra/engine/eobcommon.cpp
@@ -28,6 +28,7 @@
 #include "kyra/script/script_eob.h"
 #include "kyra/engine/timer.h"
 #include "kyra/gui/debugger.h"
+#include "kyra/gui/automap_eob.h"
 
 #include "common/config-manager.h"
 #include "common/debug-channels.h"
@@ -113,11 +114,8 @@ EoBCoreEngine::EoBCoreEngine(OSystem *system, const GameFlags &flags) : KyraRpgE
 	_preventMonsterFlash = false;
 	_sceneShakeCountdown = 0;
 
-	memset(_automapVisited, 0, sizeof(_automapVisited));
-	memset(_automapSeen, 0, sizeof(_automapSeen));
-	_automapVisible = false;
-	_automapSelectedBlock = 0xFFFF;
-	_automapEditing = false;
+	_automap = nullptr;
+	_hasTempDataMapFlags = 0;
 
 	_teleporterPulse = 0;
 
@@ -146,6 +144,7 @@ EoBCoreEngine::EoBCoreEngine(OSystem *system, const GameFlags &flags) : KyraRpgE
 	_configADDRuleEnhancements = false;
 	_configEnhancedReload = false;
 	_configNPCPatch = false;
+	_configAutomap = false;
 
 	_npcSequenceSub = 0;
 	_moveCounter = 0;
@@ -284,9 +283,6 @@ EoBCoreEngine::~EoBCoreEngine() {
 	releaseItemsAndDecorationsShapes();
 	releaseTempData();
 
-	_automapBg.free();
-	_automapFrame.free();
-
 	if (_faceShapes) {
 		for (int i = 0; i < 44; i++) {
 			if (_characters) {
@@ -370,6 +366,9 @@ EoBCoreEngine::~EoBCoreEngine() {
 	_timer = 0;
 	delete _txt;
 	_txt = 0;
+
+	delete _automap;
+	_automap = nullptr;
 }
 
 Common::KeymapArray EoBCoreEngine::initKeymaps(const Common::String &gameId) {
@@ -585,6 +584,10 @@ Common::Error EoBCoreEngine::init() {
 	memset(_monsterStoneOverlay, (_flags.platform == Common::kPlatformAmiga) ? guiSettings()->colors.guiColorWhite : 0x0D, 16 * sizeof(uint8));
 	_monsterFlashOverlay[0] = _monsterStoneOverlay[0] = 0;
 
+	// Always create this, regardless of whether the launcher option is enabled or not. Otherwise the map would
+	// be incomplete if the option is enabled later on in the game.
+	_automap = new Automap_EoB(_system, &_levelBlockProperties, _wllWallFlags, _flags.gameID, _flags.lang, true);
+
 	return Common::kNoError;
 }
 
@@ -781,50 +784,10 @@ void EoBCoreEngine::runLoop() {
 		uint32 frameEnd = _system->getMillis() + 8;
 		checkPartyStatus(true);
 		// While the map is up, suppress the play-field buttons so clicks hit the map.
-		int inputFlag = checkInput(_automapVisible ? 0 : _activeButtons, true, 0);
+		int inputFlag = checkInput(_automap->isVisible() ? 0 : _activeButtons, true, 0);
 		removeInputTop();
 
-		if (inputFlag && inputFlag == _keyMap[Common::KEYCODE_TAB]) {
-			if (_configAutomap)         // non-original automap, opt-out via game options
-				automapToggle();
-		} else if (_automapVisible && inputFlag && inputFlag == _keyMap[Common::KEYCODE_ESCAPE]) {
-			automapToggle();            // Esc also closes the map (only while it is open)
-		} else if (_automapVisible && inputFlag == 199) {
-			automapHandleClick();
-		} else if (_automapVisible && inputFlag && inputFlag == _keyMap[Common::KEYCODE_n]) {
-			automapEditNote();
-		} else if (_automapVisible && inputFlag &&
-		           (inputFlag == (_keyMap[Common::KEYCODE_UP] | 0x100) || inputFlag == (_keyMap[Common::KEYCODE_DOWN] | 0x100) ||
-		            inputFlag == (_keyMap[Common::KEYCODE_LEFT] | 0x100) || inputFlag == (_keyMap[Common::KEYCODE_RIGHT] | 0x100))) {
-			// Shift+arrows move the map selection cursor (place notes without a mouse).
-			if (inputFlag == (_keyMap[Common::KEYCODE_UP] | 0x100))
-				automapMoveSelection(0, -1);
-			else if (inputFlag == (_keyMap[Common::KEYCODE_DOWN] | 0x100))
-				automapMoveSelection(0, 1);
-			else if (inputFlag == (_keyMap[Common::KEYCODE_LEFT] | 0x100))
-				automapMoveSelection(-1, 0);
-			else
-				automapMoveSelection(1, 0);
-		} else if (_automapVisible && inputFlag &&
-		           (inputFlag == _keyMap[Common::KEYCODE_UP] || inputFlag == _keyMap[Common::KEYCODE_DOWN] ||
-		            inputFlag == _keyMap[Common::KEYCODE_LEFT] || inputFlag == _keyMap[Common::KEYCODE_RIGHT] ||
-		            inputFlag == _keyMap[Common::KEYCODE_HOME] || inputFlag == _keyMap[Common::KEYCODE_PAGEUP])) {
-			// Movement is normally button-driven, but those are suppressed now, so
-			// dispatch the keys here. The handlers only read button->index, so a dummy is fine.
-			Button dummy;
-			if (inputFlag == _keyMap[Common::KEYCODE_UP])
-				clickedUpArrow(&dummy);
-			else if (inputFlag == _keyMap[Common::KEYCODE_DOWN])
-				clickedDownArrow(&dummy);
-			else if (inputFlag == _keyMap[Common::KEYCODE_LEFT])
-				clickedLeftArrow(&dummy);
-			else if (inputFlag == _keyMap[Common::KEYCODE_RIGHT])
-				clickedRightArrow(&dummy);
-			else if (inputFlag == _keyMap[Common::KEYCODE_HOME])
-				clickedTurnLeftArrow(&dummy);
-			else
-				clickedTurnRightArrow(&dummy);
-		}
+		_automap->mainLoopProcess(this, inputFlag);
 
 		if (!_runFlag)
 			break;
@@ -836,12 +799,12 @@ void EoBCoreEngine::runLoop() {
 		bool sceneRedraw = _sceneUpdateRequired && !_sceneShakeCountdown;
 		// On any view change, mark the now-visible blocks "seen" for the map.
 		if (sceneRedraw)
-			automapMarkSeenFromCurrent();
+			_automap->markSeen(_currentBlock, _currentDirection);
 
-		if (_automapVisible) {
+		if (_automap->isVisible()) {
 			// Opaque overlay: skip the dungeon redraw behind it (redrawn on close).
 			_sceneUpdateRequired = false;
-			automapDraw();
+			_automap->draw(this);
 		} else if (sceneRedraw) {
 			drawScene(1);
 		}
diff --git a/engines/kyra/engine/eobcommon.h b/engines/kyra/engine/eobcommon.h
index bcaa90375f9..52ecdcabe0c 100644
--- a/engines/kyra/engine/eobcommon.h
+++ b/engines/kyra/engine/eobcommon.h
@@ -259,6 +259,7 @@ struct EoBMenuButtonDef {
 };
 
 class EoBInfProcessor;
+class Automap_EoB;
 
 class EoBCoreEngine : public KyraRpgEngine {
 friend class TextDisplayer_rpg;
@@ -268,6 +269,7 @@ friend class EoBInfProcessor;
 friend class DarkmoonSequenceHelper;
 friend class CharacterGenerator;
 friend class TransferPartyWiz;
+friend class Automap_EoB; // TODO: REMOVE
 public:
 	EoBCoreEngine(OSystem *system, const GameFlags &flags);
 	~EoBCoreEngine() override;
@@ -652,66 +654,9 @@ protected:
 	void drawSceneShapes(int start = 0, int end = 18, int drawFlags = 0xFF);
 	void drawDecorations(int index);
 
-	// Automap (non-original): a north-up map toggled with Tab. "Visited" cells were
-	// walked, "seen" cells only glimpsed (drawn dimmer); one bit each, per level.
-	void automapSetBit(uint8 (&bits)[20][128], uint16 block) {
-		if (_currentLevel < 20 && block < 1024)
-			bits[_currentLevel][block >> 3] |= (1 << (block & 7));
-	}
-	bool automapGetBit(const uint8 (&bits)[20][128], uint16 block) const {
-		return _currentLevel < 20 && block < 1024 && (bits[_currentLevel][block >> 3] & (1 << (block & 7)));
-	}
-	void automapMarkVisited(uint16 block) { automapSetBit(_automapVisited, block); }
-	bool automapIsVisited(uint16 block) const { return automapGetBit(_automapVisited, block); }
-	void automapMarkSeen(uint16 block) { automapSetBit(_automapSeen, block); }
-	bool automapIsSeen(uint16 block) const { return automapGetBit(_automapSeen, block); }
-	void automapMarkSeenFromCurrent();
-	void automapToggle();
-	void automapDraw();
-
-	// Geometry shared by drawing and click hit-testing.
-	struct AutomapLayout {
-		int cell;
-		int offX, offY;                 // top-left of the 32x32 grid (mouse hit-test)
-		int frame;                      // stone frame thickness
-		int mapX, mapY, mapW, mapH;     // parchment region (map + footer strip)
-		int sideX, sideY, sideW, sideH; // stone side panel (plaque, legend, keys)
-		int plX, plY, plW, plH;         // level/coords plaque inside the side panel
-		int footY;                      // top of the selected-note footer strip
-	};
-	AutomapLayout automapLayout() const;
-	// Area name for the plaque; falls back to "Level N" where unknown.
-	Common::String automapLevelName() const;
-	uint32 automapNoteKey(uint16 block) const { return ((uint32)_currentLevel << 16) | block; }
-	// Per-cell map notes (non-original): free text, keyed per level. Click selects, N edits.
-	void automapHandleClick();
-	void automapEditNote();
-
-	// Auto-collected per-cell info (separate from manual notes): a glyph code plus a
-	// short description, learned by observation. Floor items are read live, not stored.
-	enum AutomapIcon { kAmNone = 0, kAmStairsDown = 1, kAmStairsUp = 2, kAmTeleport = 3 };
-	void automapCollectCellInfo(uint16 block);
-	void automapTagTransition(int fromLevel, uint16 fromBlock, int toLevel);
-	Common::String automapLiveItems(uint16 block) const;
-	void automapDrawIcon(Graphics::Surface &surf, int sx, int sy, int cell, uint8 icon, uint32 color) const;
-	// Cached static chrome, rebuilt only on overlay resize; _automapFrame is the
-	// reused per-frame compositing surface (so the draw loop allocates nothing).
-	Graphics::Surface _automapBg;
-	Graphics::Surface _automapFrame;
-
-	uint8 _automapVisited[20][128];
-	uint8 _automapSeen[20][128];
-	bool _automapVisible;
-	Common::HashMap<uint32, Common::String> _automapNotes;
-	Common::HashMap<uint32, uint8> _automapIcons;
-	Common::HashMap<uint32, Common::String> _automapAutoInfo;
-	uint16 _automapSelectedBlock;
-	bool _automapEditing;
-	Common::String _automapEditBuffer;
-	// Transient per-cell door bits (1 = N/S leaf, 2 = E/W), so an open door (which
-	// loses its wall flag while animating) still draws. Rebuilt each draw, not saved.
-	Common::HashMap<uint32, uint8> _automapDoorBits;
-	void automapMoveSelection(int dx, int dy);
+	// Automap (non-original): a north-up map toggled with Tab
+	Automap_EoB *_automap;
+	uint32 _hasTempDataMapFlags;
 
 	int calcNewBlockPositionAndTestPassability(uint16 curBlock, uint16 direction);
 	void notifyBlockNotPassable();
@@ -1025,12 +970,15 @@ protected:
 	Common::String readOriginalSaveFile(const Common::Path &file);
 	bool saveAsOriginalSaveFile(int slot = -1);
 
-	void *generateMonsterTempData(LevelTempData *tmp) override;
+	const void *generateMonsterTempData(uint8 &monsterDifficulty) const override;
 	void restoreMonsterTempData(LevelTempData *tmp) override;
 	void releaseMonsterTempData(LevelTempData *tmp) override;
-	void *generateWallOfForceTempData(LevelTempData *tmp) override;
+	const void *generateWallOfForceTempData() const override;
 	void restoreWallOfForceTempData(LevelTempData *tmp) override;
 	void releaseWallOfForceTempData(LevelTempData *tmp) override;
+	const void *generateAutoMapTempData() override;
+	void restoreAutoMapTempData(LevelTempData *tmp) override;
+	void releaseAutoMapTempData(LevelTempData *tmp) override;
 
 	const char *const *_saveLoadStrings;
 
diff --git a/engines/kyra/engine/kyra_rpg.h b/engines/kyra/engine/kyra_rpg.h
index 24eed7f7fae..4ae5d139f7a 100644
--- a/engines/kyra/engine/kyra_rpg.h
+++ b/engines/kyra/engine/kyra_rpg.h
@@ -62,12 +62,14 @@ struct OpenDoorState {
 };
 
 struct LevelTempData {
-	uint8 *wallsXorData;
-	uint16 *flags;
-	void *monsters;
-	void *flyingObjects;
-	void *wallsOfForce;
+	const uint8 *wallsXorData;
+	const uint16 *flags;
+	const void *monsters;
+	const void *flyingObjects;
+	const void *wallsOfForce;
 	uint8 monsterDifficulty;
+	// ScummVM extra feature for EOBI/II
+	const void *automapExploreState;
 };
 
 struct EoBFlyingObject {
@@ -517,15 +519,18 @@ protected:
 	void generateTempData();
 	virtual void restoreBlockTempData(int levelIndex);
 	void releaseTempData();
-	virtual void *generateMonsterTempData(LevelTempData *tmp) = 0;
+	virtual const void *generateMonsterTempData(uint8 &monsterDifficulty) const = 0;
 	virtual void restoreMonsterTempData(LevelTempData *tmp) = 0;
 	virtual void releaseMonsterTempData(LevelTempData *tmp) = 0;
 	void restoreFlyingObjectTempData(LevelTempData *tmp);
-	void *generateFlyingObjectTempData(LevelTempData *tmp);
+	const void *generateFlyingObjectTempData() const;
 	void releaseFlyingObjectTempData(LevelTempData *tmp);
-	virtual void *generateWallOfForceTempData(LevelTempData *tmp) { return 0; }
+	virtual const void *generateWallOfForceTempData() const { return nullptr; }
 	virtual void restoreWallOfForceTempData(LevelTempData *tmp) {}
 	virtual void releaseWallOfForceTempData(LevelTempData *tmp) {}
+	virtual const void *generateAutoMapTempData() { return nullptr; }
+	virtual void restoreAutoMapTempData(LevelTempData *tmp) {}
+	virtual void releaseAutoMapTempData(LevelTempData *tmp) {}
 
 	LevelTempData *_lvlTempData[29];
 	const int _numFlyingObjects;
diff --git a/engines/kyra/engine/lol.h b/engines/kyra/engine/lol.h
index 0e9b5d1403b..2cfadf37279 100644
--- a/engines/kyra/engine/lol.h
+++ b/engines/kyra/engine/lol.h
@@ -1313,7 +1313,7 @@ private:
 	Common::Error loadGameState(int slot) override;
 	Common::Error saveGameStateIntern(int slot, const char *saveName, const Graphics::Surface *thumbnail) override;
 
-	void *generateMonsterTempData(LevelTempData *tmp) override;
+	const void *generateMonsterTempData(uint8 &monsterDifficulty) const override;
 	void restoreBlockTempData(int levelIndex) override;
 	void restoreMonsterTempData(LevelTempData *tmp) override;
 	void releaseMonsterTempData(LevelTempData *tmp) override;
diff --git a/engines/kyra/engine/scene_eob.cpp b/engines/kyra/engine/scene_eob.cpp
index a2251d8e645..51bbf4ac011 100644
--- a/engines/kyra/engine/scene_eob.cpp
+++ b/engines/kyra/engine/scene_eob.cpp
@@ -22,6 +22,7 @@
 #ifdef ENABLE_EOB
 
 #include "kyra/engine/eobcommon.h"
+#include "kyra/gui/automap_eob.h"
 #include "kyra/resource/resource.h"
 #include "kyra/script/script_eob.h"
 #include "kyra/engine/timer.h"
@@ -751,7 +752,8 @@ void EoBCoreEngine::moveParty(uint16 block) {
 	updateAllMonsterDests();
 	uint16 old = _currentBlock;
 	_currentBlock = block;
-	automapMarkVisited(block);
+
+	_automap->markVisited(block);
 
 	// Note the level before running scripts: if one moves the party elsewhere, the
 	// automap tags this cell as an exit.
@@ -768,9 +770,9 @@ void EoBCoreEngine::moveParty(uint16 block) {
 	runLevelScript(block, 1);
 
 	if (_currentLevel != preLevel)
-		automapTagTransition(preLevel, trigBlock, _currentLevel);
+		_automap->tagTransition(preLevel, trigBlock, _currentLevel);
 	else
-		automapCollectCellInfo(block);
+		_automap->collectCellInfo(preLevel, trigBlock, _teleporterWallId);
 
 	if (_flags.gameID == GI_EOB2 && _levelBlockProperties[block].walls[0] == 26)
 		memset(_levelBlockProperties[block].walls, 0, 4);
diff --git a/engines/kyra/gui/automap_eob.cpp b/engines/kyra/gui/automap_eob.cpp
new file mode 100644
index 00000000000..21da897abfd
--- /dev/null
+++ b/engines/kyra/gui/automap_eob.cpp
@@ -0,0 +1,975 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifdef ENABLE_EOB
+
+#include "graphics/font.h"
+#include "graphics/fontman.h"
+#include "graphics/surface.h"
+#include "kyra/engine/kyra_rpg.h"
+#include "kyra/gui/automap_eob.h"
+
+#include "common/hashmap.h"	// TODO: REMOVE
+#include "common/savefile.h" // TODO: REMOVE
+#include "backends/keymapper/keymapper.h"         // TODO: REMOVE
+#include "kyra/engine/eobcommon.h" // TODO: REMOVE
+
+namespace Kyra {
+
+static void automapDrawBigString(Graphics::Surface &dst, const Graphics::Font *font,
+								 const Common::String &text, int x, int y, int w, uint32 color, int scale,
+								 Graphics::TextAlign align = Graphics::kTextAlignCenter) {
+	if (!font || text.empty() || scale < 1)
+		return;
+	const int tw = font->getStringWidth(text);
+	const int th = font->getFontHeight();
+	if (tw <= 0 || th <= 0)
+		return;
+
+	if (scale == 1) {
+		font->drawString(&dst, text, x, y, w, color, align);
+		return;
+	}
+
+	// The overlay may be 16- or 32-bit; index pixels by byte width.
+	const int bpp = dst.format.bytesPerPixel;
+	if (bpp != 2 && bpp != 4) {
+		font->drawString(&dst, text, x, y, w, color, align);
+		return;
+	}
+
+	// Render onto a zeroed scratch surface so inked pixels are distinguishable.
+	Graphics::Surface tmp;
+	tmp.create(tw, th, dst.format);
+	tmp.fillRect(Common::Rect(0, 0, tw, th), 0);
+	font->drawString(&tmp, text, 0, 0, tw, color, Graphics::kTextAlignLeft);
+
+	const int dw = tw * scale;
+	int dx = x + (w - dw) / 2; // center (default)
+	if (align == Graphics::kTextAlignLeft)
+		dx = x;
+	else if (align == Graphics::kTextAlignRight)
+		dx = x + w - dw;
+	if (dx < x)
+		dx = x;
+	for (int sy = 0; sy < th; ++sy) {
+		const byte *src = (const byte *)tmp.getBasePtr(0, sy);
+		for (int sx = 0; sx < tw; ++sx) {
+			const uint32 c = (bpp == 2) ? ((const uint16 *)src)[sx] : ((const uint32 *)src)[sx];
+			if (c == 0) // transparent background pixel
+				continue;
+			const int px = dx + sx * scale;
+			const int py = y + sy * scale;
+			dst.fillRect(Common::Rect(px, py, px + scale, py + scale), c);
+		}
+	}
+	tmp.free();
+}
+
+// Solid triangle by scanlines (party arrow).
+static void automapFillTri(Graphics::Surface &s, int ax, int ay, int bx, int by, int cx, int cy, uint32 color) {
+	// Sort vertices by y.
+	if (ay > by) {
+		SWAP(ax, bx);
+		SWAP(ay, by);
+	}
+	if (ay > cy) {
+		SWAP(ax, cx);
+		SWAP(ay, cy);
+	}
+	if (by > cy) {
+		SWAP(bx, cx);
+		SWAP(by, cy);
+	}
+	if (cy == ay)
+		return;
+
+	for (int y = ay; y <= cy; ++y) {
+		int xLong = ax + (cx - ax) * (y - ay) / (cy - ay);
+		int xShort = (y < by)
+						 ? (by == ay ? ax : ax + (bx - ax) * (y - ay) / (by - ay))
+						 : (cy == by ? bx : bx + (cx - bx) * (y - by) / (cy - by));
+		int x0 = MIN(xLong, xShort);
+		int x1 = MAX(xLong, xShort);
+		s.hLine(x0, y, x1, color);
+	}
+}
+
+// Sparse two-tone speckle to fake stone/paper grain (overwrites pixels, no blend).
+static void automapNoise(Graphics::Surface &s, int x0, int y0, int w, int h, uint32 cA, uint32 cB, uint32 seed) {
+	if (w <= 0 || h <= 0 || x0 < 0 || y0 < 0 || x0 + w > s.w || y0 + h > s.h)
+		return;
+	const int bpp = s.format.bytesPerPixel;
+	if (bpp != 2 && bpp != 4)
+		return;
+	uint32 st = seed | 1u;
+	for (int y = 0; y < h; ++y) {
+		byte *row = (byte *)s.getBasePtr(x0, y0 + y);
+		for (int x = 0; x < w; ++x) {
+			st = st * 1664525u + 1013904223u;
+			const uint32 v = st >> 24; // 0..255
+			if (v < 26) {
+				const uint32 c = (v & 1u) ? cA : cB;
+				if (bpp == 2)
+					((uint16 *)row)[x] = (uint16)c;
+				else
+					((uint32 *)row)[x] = c;
+			}
+		}
+	}
+}
+
+static void automapBevel(Graphics::Surface &s, const Common::Rect &r, uint32 hi, uint32 lo) {
+	s.hLine(r.left, r.top, r.right - 1, hi);
+	s.vLine(r.left, r.top, r.bottom - 1, hi);
+	s.hLine(r.left, r.bottom - 1, r.right - 1, lo);
+	s.vLine(r.right - 1, r.top, r.bottom - 1, lo);
+}
+
+static void automapRivet(Graphics::Surface &s, int x, int y, int sz, uint32 dark, uint32 mid, uint32 hi) {
+	s.fillRect(Common::Rect(x - sz, y - sz, x + sz + 1, y + sz + 1), dark);
+	s.fillRect(Common::Rect(x - sz + 1, y - sz + 1, x + sz, y + sz), mid);
+	const int q = MAX(1, sz / 2);
+	s.fillRect(Common::Rect(x - sz + 1, y - sz + 1, x - sz + 1 + q, y - sz + 1 + q), hi);
+}
+
+static int automapFit(const Graphics::Font *f, const Common::String &str, int maxW, int maxSc) {
+	int sc = MAX(1, maxSc);
+	while (sc > 1 && f && f->getStringWidth(str) * sc > maxW)
+		--sc;
+	return sc;
+}
+
+// Automap strings that should be translated to the game's language. Currently, only English is available.
+// TODO: Probably move to KYRA.DAT eventually.
+const Automap_EoB::TranslateableStrings Automap_EoB::_stringTable[] = {
+	{
+		{
+			"- LEGEND -",
+			"Party",
+			"Stairs up",
+			"Stairs down",
+			"Teleporter",
+			"Door",
+			"Lever",
+			"Niche",
+			"Note"
+		},
+
+		// Area names for the plaque. The games have no level names in their data and the file
+		// names of the graphics and sound data files provide only vague hints. So we hardcode
+		// the level names here. The Sega CD version of EOBI has an automap, but it doesn't use
+		// level names. The maps are just called "L%d" or "Floor %d" (%d being the level number).
+		// The FM-Towns version of EOBII has level names for its autogenerated savegame names
+		// which are similar to what we do (names based on the graphics/sound files).
+		{
+			{
+				"Upper Sewers",
+				"Middle Sewers",
+				"Lower Sewers",
+				"Dwarven Ruins I",
+				"Dwarven Camp",
+				"Dwarven Ruins III",
+				"Upper Drow",
+				"Drow Outcasts",
+				"Lower Drow",
+				"Mantis Hive",
+				"Xanathar Sanctum",
+				"Xanathar's Lair",
+				"",
+				"",
+				"",
+				""
+			},
+			{
+				"Catacombs I",
+				"Catacombs II",
+				"Catacombs III",
+				"Forest",
+				"Darkmoon Temple",
+				"Temple Mezzanine",
+				"Silver Tower I",
+				"Silver Tower II",
+				"Silver Tower III",
+				"Azure Tower I",
+				"Azure Tower II",
+				"Azure Tower III",
+				"Azure Tower IV",
+				"Azure Tower V",
+				"Crimson Tower I",
+				"Crimson Tower II"
+			}
+		}
+	},
+	{
+		{
+			"", "", "", "", "", "", "", "", ""
+		},
+		{
+			{
+				"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ""
+			},
+			{
+				"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ""
+			}
+		}
+	}
+};
+
+Automap_EoB::Automap_EoB(OSystem *system, LevelBlockProperty **blockData, const uint8 *wllFlags, int gameID, int lang, bool featureEnabled) :
+	_system(system), _blockData(*blockData), _wllWallFlags(wllFlags), _enabled(featureEnabled), _visible(false), _selectedBlock(0xFFFF), _editing(false), _automapBg(nullptr),
+	_automapFrame(nullptr), _levelNames(nullptr), _legendStrings(nullptr), _numLevelNames(gameID == GI_EOB1 ? 12 : (gameID == GI_EOB2 ? 16 : 0)) {
+	_automapBg = new Graphics::Surface();
+	_automapFrame = new Graphics::Surface();
+
+	uint langIndex = 0;
+	uint gameIndex = gameID - GI_EOB1;
+
+	switch (lang) {
+	case Common::EN_ANY:
+	default:
+		break;
+	}
+
+	assert(langIndex < ARRAYSIZE(_stringTable));
+	assert(gameIndex < ARRAYSIZE(_stringTable[0].levelNames));
+
+	_legendStrings = _stringTable[langIndex].legendStrings;
+	_levelNames = _stringTable[langIndex].levelNames[gameIndex];
+}
+
+Automap_EoB::~Automap_EoB() {
+	delete _automapBg;
+	delete _automapFrame;
+}
+
+void Automap_EoB::markVisited(uint16 block) {
+	assert(block < 1024);
+	_blockData[block].direction |= 2;
+}
+
+bool Automap_EoB::isVisited(uint16 block) const {
+	assert(block < 1024);
+	return (_blockData[block].direction & 2) != 0;
+}
+
+void Automap_EoB::markSeen(uint16 block, int8 dir) {
+	assert(block < 1024);
+
+	// Mark the cells in the line of sight ahead up to the first wall or enemy (but include the block with the enemy).
+	// Also 1 block diagonal to the left/right of the line of sight, to have a bit more realistic view (and not make
+	// players walk around pointlessly just to complete the map). 
+	// First value is the relative direction to move, next is whether to reveal the block or not. We don't reveal the
+	// blocks directly to the left/right of the party (behind line of sight), even if our route passes through them.
+	static const int8 traceRoutes[5][4] = {
+		{ -1,  0,  0,  1 },
+		{  0,  0, -1,  1 },
+		{  0,  1,  0,  1 },
+		{  0,  0,  1,  1 },
+		{  1,  0,  0,  1 }
+	};	
+
+	for (int i = 0; i < ARRAYSIZE(traceRoutes); ++i) {
+		uint16 b = block;
+		const int8 *r = traceRoutes[i];
+		for (int ii = 0; ii < 2; ++ii) {
+			int8 d = (dir + *r++) & 3;
+			const uint16 nb = calcNewBlockPosition(b, d);
+			bool reveal = *r++;
+			if (!(_wllWallFlags[_blockData[nb].walls[d ^ 2]] & 1) || _blockData[b].flags & 7)
+				break;
+			b = nb;
+			if (reveal)
+				_blockData[b].direction |= 1;
+		}
+	}
+}
+
+bool Automap_EoB::isSeen(uint16 block) const {
+	assert(block < 1024);
+	return (_blockData[block].direction & 1) != 0;
+}
+
+void Automap_EoB::tagTransition(int fromLevel, uint16 fromBlock, int toLevel) {
+	// On a script-driven level change, tag the cell we left with a stairs/teleport
+	// glyph and a "to level N" note (deeper level = down).
+	if (fromLevel < 0 || fromLevel >= 20 || fromBlock >= 1024)
+		return;
+	const uint32 k = ((uint32)fromLevel << 16) | fromBlock;
+	uint8 icon;
+	Common::String info;
+	if (toLevel > fromLevel) {
+		icon = kAmStairsDown;
+		info = Common::String::format("Down to level %d", toLevel);
+	} else if (toLevel < fromLevel) {
+		icon = kAmStairsUp;
+		info = Common::String::format("Up to level %d", toLevel);
+	} else {
+		icon = kAmTeleport;
+		info = Common::String::format("Teleport to level %d", toLevel);
+	}
+	_automapIcons[k] = icon;
+	_automapAutoInfo[k] = info;
+}
+
+void Automap_EoB::collectCellInfo(int level, uint16 block, int teleporterWallId) {
+	if (level >= 20 || block >= 1024)
+		return;
+	const uint32 k = noteKey(level, block);
+	if (_automapIcons.contains(k)) // already classified (e.g. as stairs)
+		return;
+	for (int d = 0; d < 4; ++d) {
+		if (_blockData[block].walls[d] == teleporterWallId) {
+			_automapIcons[k] = kAmTeleport;
+			if (!_automapAutoInfo.contains(k))
+				_automapAutoInfo[k] = "Teleporter";
+			break;
+		}
+	}
+}
+
+void Automap_EoB::mainLoopProcess(EoBCoreEngine *vm, int inputFlag) {
+	if (!_enabled)
+		return;
+
+	if (inputFlag == vm->_keyMap[Common::KEYCODE_TAB] || (_visible && inputFlag == vm->_keyMap[Common::KEYCODE_ESCAPE])) {
+		_selectedBlock = vm->_currentBlock;
+		_visible = !_visible;
+		if (_visible) {
+			_system->showOverlay(true);
+		} else {
+			_system->hideOverlay();
+			vm->_sceneUpdateRequired = true;
+		}
+	} else if (_visible && inputFlag == 199) {
+		handleClick(vm);
+	} else if (_visible && inputFlag && inputFlag == vm->_keyMap[Common::KEYCODE_n]) {
+		_selectedBlock = vm->_currentBlock;
+		editNote(vm);
+	} else if (_visible && inputFlag &&
+		(inputFlag == (vm->_keyMap[Common::KEYCODE_UP] | 0x100) || inputFlag == (vm->_keyMap[Common::KEYCODE_DOWN] | 0x100) ||
+			inputFlag == (vm->_keyMap[Common::KEYCODE_LEFT] | 0x100) || inputFlag == (vm->_keyMap[Common::KEYCODE_RIGHT] | 0x100))) {
+		// Shift+arrows move the map selection cursor (place notes without a mouse).
+		if (inputFlag == (vm->_keyMap[Common::KEYCODE_UP] | 0x100))
+			moveSelection(0, -1);
+		else if (inputFlag == (vm->_keyMap[Common::KEYCODE_DOWN] | 0x100))
+			moveSelection(0, 1);
+		else if (inputFlag == (vm->_keyMap[Common::KEYCODE_LEFT] | 0x100))
+			moveSelection(-1, 0);
+		else
+			moveSelection(1, 0);
+	} else if (_visible && inputFlag &&
+			   (inputFlag == vm->_keyMap[Common::KEYCODE_UP] || inputFlag == vm->_keyMap[Common::KEYCODE_DOWN] ||
+				inputFlag == vm->_keyMap[Common::KEYCODE_LEFT] || inputFlag == vm->_keyMap[Common::KEYCODE_RIGHT] ||
+				inputFlag == vm->_keyMap[Common::KEYCODE_HOME] || inputFlag == vm->_keyMap[Common::KEYCODE_PAGEUP])) {
+		// Movement is normally button-driven, but those are suppressed now, so
+		// dispatch the keys here. The handlers only read button->index, so a dummy is fine.
+		Button dummy;
+		if (inputFlag == vm->_keyMap[Common::KEYCODE_UP])
+			vm->clickedUpArrow(&dummy);
+		else if (inputFlag == vm->_keyMap[Common::KEYCODE_DOWN])
+			vm->clickedDownArrow(&dummy);
+		else if (inputFlag == vm->_keyMap[Common::KEYCODE_LEFT])
+			vm->clickedLeftArrow(&dummy);
+		else if (inputFlag == vm->_keyMap[Common::KEYCODE_RIGHT])
+			vm->clickedRightArrow(&dummy);
+		else if (inputFlag == vm->_keyMap[Common::KEYCODE_HOME])
+			vm->clickedTurnLeftArrow(&dummy);
+		else
+			vm->clickedTurnRightArrow(&dummy);
+	}
+}
+
+void Automap_EoB::draw(EoBCoreEngine *vm) {
+	markVisited(vm->_currentBlock);
+
+	const int ow = _system->getOverlayWidth();
+	const int oh = _system->getOverlayHeight();
+	const Graphics::PixelFormat fmt = _system->getOverlayFormat();
+	const AutomapLayout L = createLayout();
+
+	// "Stone tablet" palette: opaque grey stone + aged parchment
+	const uint32 cStone = fmt.RGBToColor(0x54, 0x56, 0x5e);
+	const uint32 cStoneDark = fmt.RGBToColor(0x3c, 0x3e, 0x45);
+	const uint32 cStoneEdge = fmt.RGBToColor(0x23, 0x25, 0x2a);
+	const uint32 cStoneHi = fmt.RGBToColor(0x6b, 0x6e, 0x78);
+	const uint32 cRivet = fmt.RGBToColor(0xc2, 0xc5, 0xcd);
+	const uint32 cPaper = fmt.RGBToColor(0xd6, 0xbf, 0x94);
+	const uint32 cPaperHi = fmt.RGBToColor(0xe2, 0xcd, 0xa4);
+	const uint32 cPaperLo = fmt.RGBToColor(0xc4, 0xab, 0x78);
+	const uint32 cPaperEdge = fmt.RGBToColor(0xa9, 0x8b, 0x56);
+	const uint32 cInk = fmt.RGBToColor(0x3a, 0x2a, 0x18);
+	const uint32 cInkSoft = fmt.RGBToColor(0x7a, 0x60, 0x38);
+	const uint32 cFloor = fmt.RGBToColor(0xbb, 0x9c, 0x5e); // a touch darker than paper
+	const uint32 cFloorSeen = fmt.RGBToColor(0xc7, 0xb1, 0x80);
+	const uint32 cGrid = fmt.RGBToColor(0x8a, 0x70, 0x38);
+	const uint32 cWall = fmt.RGBToColor(0x2c, 0x1e, 0x10);
+	const uint32 cWallSeen = fmt.RGBToColor(0x8a, 0x73, 0x4a);
+	const uint32 cDoor = fmt.RGBToColor(0x7a, 0x4a, 0x1c);
+	const uint32 cStair = fmt.RGBToColor(0x3f, 0x7a, 0x3a);
+	const uint32 cTele = fmt.RGBToColor(0x5a, 0x3f, 0x8a);
+	const uint32 cLever = fmt.RGBToColor(0x9a, 0x2d, 0x2d);
+	const uint32 cNiche = fmt.RGBToColor(0x8a, 0x5a, 0x1c);
+	const uint32 cNote = fmt.RGBToColor(0x9a, 0x6a, 0x14);
+	const uint32 cParty = fmt.RGBToColor(0xa8, 0x28, 0x1c);
+	const uint32 cPartyEdge = fmt.RGBToColor(0x5a, 0x14, 0x0e);
+	const uint32 cSel = fmt.RGBToColor(0x7a, 0x2a, 0x18);
+	const uint32 cPlaqueBg = fmt.RGBToColor(0x1c, 0x1a, 0x16);
+	const uint32 cPlaqueEd = fmt.RGBToColor(0x0d, 0x0c, 0x0a);
+	const uint32 cGold = fmt.RGBToColor(0xf0, 0xc8, 0x50);
+	const uint32 cGoldDim = fmt.RGBToColor(0xb8, 0x92, 0x3a);
+	const uint32 cPanelTxt = fmt.RGBToColor(0xd8, 0xc8, 0xa8);
+
+	const Graphics::Font *bigFont = FontMan.getFontByUsage(Graphics::FontManager::kBigGUIFont);
+	const int fh = bigFont ? bigFont->getFontHeight() : 8;
+	const int sc = CLIP<int>(oh / 320, 1, 3); // base side-panel text scale
+	const int mpad = MAX(8, L.mapW / 40);
+
+	// (Re)build the cached map background on an overlay resize.
+	if (_automapBg->w != ow || _automapBg->h != oh) {
+		_automapBg->free();
+		_automapBg->create(ow, oh, fmt);
+		_automapFrame->free();
+		_automapFrame->create(ow, oh, fmt);
+		Graphics::Surface &bg = *_automapBg;
+
+		bg.fillRect(Common::Rect(0, 0, ow, oh), cStone);
+		automapNoise(bg, 0, 0, ow, oh, cStoneHi, cStoneEdge, 0x9e3779b9u);
+		automapBevel(bg, Common::Rect(0, 0, ow, oh), cStoneHi, cStoneEdge);
+		automapBevel(bg, Common::Rect(2, 2, ow - 2, oh - 2), cStone, cStoneEdge);
+		const int rv = MAX(2, L.frame / 4);
+		const int ri = L.frame / 2;
+		automapRivet(bg, ri, ri, rv, cStoneEdge, cStoneHi, cRivet);
+		automapRivet(bg, ow - ri, ri, rv, cStoneEdge, cStoneHi, cRivet);
+		automapRivet(bg, ri, oh - ri, rv, cStoneEdge, cStoneHi, cRivet);
+		automapRivet(bg, ow - ri, oh - ri, rv, cStoneEdge, cStoneHi, cRivet);
+
+		const Common::Rect mr(L.mapX, L.mapY, L.mapX + L.mapW, L.mapY + L.mapH);
+		bg.fillRect(mr, cPaperEdge);
+		bg.fillRect(Common::Rect(mr.left + 2, mr.top + 2, mr.right - 2, mr.bottom - 2), cPaper);
+		automapNoise(bg, mr.left + 2, mr.top + 2, L.mapW - 4, L.mapH - 4, cPaperHi, cPaperLo, 0x85ebca6bu);
+		bg.frameRect(Common::Rect(mr.left + 2, mr.top + 2, mr.right - 2, mr.bottom - 2), cInkSoft);
+		bg.frameRect(Common::Rect(mr.left + 5, mr.top + 5, mr.right - 5, mr.bottom - 5), cInk);
+		bg.hLine(L.mapX + mpad, L.footY, L.mapX + L.mapW - mpad, cInkSoft);
+
+		const Common::Rect sr(L.sideX, L.sideY, L.sideX + L.sideW, L.sideY + L.sideH);
+		bg.fillRect(sr, cStoneDark);
+		automapNoise(bg, sr.left, sr.top, L.sideW, L.sideH, cStoneHi, cStoneEdge, 0xc2b2ae35u);
+		automapBevel(bg, sr, cStoneEdge, cStoneHi); // sunken: dark top/left
+
+		// Plaque face
+		bg.fillRect(Common::Rect(L.plX, L.plY, L.plX + L.plW, L.plY + L.plH), cPlaqueEd);
+		bg.fillRect(Common::Rect(L.plX + 2, L.plY + 2, L.plX + L.plW - 2, L.plY + L.plH - 2), cPlaqueBg);
+
+		const int lx = L.plX, colW = L.plW;
+		const int rowH = fh * sc + MAX(4, sc * 4);
+		int cyy = L.plY + L.plH + MAX(8, sc * 6);
+
+		automapDrawBigString(bg, bigFont, _legendStrings[0], lx, cyy, colW, cGoldDim, sc);
+		cyy += fh * sc + MAX(4, sc * 3);
+		bg.hLine(lx, cyy, lx + colW - 1, cStoneHi);
+		bg.hLine(lx, cyy + 1, lx + colW - 1, cStoneEdge);
+		cyy += MAX(6, sc * 4);
+
+		const int isz = MAX(6, fh * sc);
+		for (int i = 0; i < 8; ++i) {
+			const int ix = lx, iy = cyy;
+			switch (i) {
+			case 0:
+				automapFillTri(bg, ix + isz / 2, iy, ix + isz, iy + isz, ix, iy + isz, cParty);
+				break; // party (up)
+			case 1:
+				automapFillTri(bg, ix, iy + isz, ix + isz, iy + isz, ix + isz / 2, iy, cStair);
+				break; // stairs up
+			case 2:
+				automapFillTri(bg, ix, iy, ix + isz, iy, ix + isz / 2, iy + isz, cStair);
+				break; // stairs down
+			case 3:
+				bg.frameRect(Common::Rect(ix, iy, ix + isz, iy + isz), cTele);
+				bg.frameRect(Common::Rect(ix + 1, iy + 1, ix + isz - 1, iy + isz - 1), cTele);
+				break; // teleporter
+			case 4:
+				bg.fillRect(Common::Rect(ix, iy + isz / 3, ix + isz, iy + isz - isz / 3), cDoor);
+				break; // door
+			case 5:
+				bg.fillRect(Common::Rect(ix + isz / 3, iy, ix + isz - isz / 3, iy + isz), cLever);
+				break; // lever
+			case 6:
+				bg.fillRect(Common::Rect(ix + isz / 3, iy, ix + isz - isz / 3, iy + isz), cNiche);
+				break; // niche
+			default:
+				bg.fillRect(Common::Rect(ix, iy, ix + isz, iy + isz), cNote);
+				break; // note
+			}
+			const int tx = lx + isz + MAX(4, sc * 2);
+			const int tw = colW - isz - MAX(4, sc * 2);
+			const int lsc = automapFit(bigFont, _legendStrings[i + 1], tw, sc);
+			automapDrawBigString(bg, bigFont, _legendStrings[i + 1], tx, iy + (isz - fh * lsc) / 2, tw, cPanelTxt, lsc, Graphics::kTextAlignLeft);
+			cyy += rowH;
+		}
+
+		cyy += MAX(6, sc * 5);
+		automapDrawBigString(bg, bigFont, "- KEYS -", lx, cyy, colW, cGoldDim, sc);
+		cyy += fh * sc + MAX(4, sc * 3);
+		bg.hLine(lx, cyy, lx + colW - 1, cStoneHi);
+		bg.hLine(lx, cyy + 1, lx + colW - 1, cStoneEdge);
+		cyy += MAX(6, sc * 4);
+		const int chipPad = MAX(2, sc * 2);
+		const int chipW = (bigFont ? bigFont->getStringWidth("N") * sc : 6 * sc) + chipPad * 2;
+		const int chipH = fh * sc + chipPad;
+		bg.fillRect(Common::Rect(lx, cyy, lx + chipW, cyy + chipH), cPlaqueEd);
+		bg.fillRect(Common::Rect(lx + 1, cyy + 1, lx + chipW - 1, cyy + chipH - 1), cPlaqueBg);
+		automapDrawBigString(bg, bigFont, "N", lx, cyy + chipPad / 2, chipW, cGold, sc);
+		automapDrawBigString(bg, bigFont, "Add note", lx + chipW + MAX(4, sc * 3), cyy + (chipH - fh * sc) / 2,
+							 colW - chipW - MAX(4, sc * 3), cPanelTxt, sc, Graphics::kTextAlignLeft);
+	}
+
+	// Copy the chrome into the reused surface, then draw dynamics on top.
+	Graphics::Surface &surf = *_automapFrame;
+	surf.copyRectToSurface(*_automapBg, 0, 0, Common::Rect(0, 0, ow, oh));
+
+	const int cell = L.cell;
+	const int offX = L.offX;
+	const int offY = L.offY;
+	const int wt = MAX(2, cell / 5); // wall bar thickness
+
+	for (int by = 0; by < 32; ++by) {
+		for (int bx = 0; bx < 32; ++bx) {
+			const uint16 block = (by << 5) | bx;
+			const bool visited = isVisited(block);
+			const bool seen = !visited && isSeen(block);
+			if (!visited && !seen)
+				continue;
+
+			const int sx = offX + bx * cell;
+			const int sy = offY + by * cell;
+
+			bool wall[4];
+			for (int d = 0; d < 4; ++d) {
+				const uint16 nb = calcNewBlockPosition(block, d);
+				wall[d] = !(_wllWallFlags[_blockData[nb].walls[d ^ 2]] & 1);
+			}
+
+			// Cache the door bits: an open door loses its wall flag mid-animation but
+			// should still draw as a door.
+			const LevelBlockProperty *bp = &_blockData[block];
+			const uint32 bkey = noteKey(vm->_currentLevel, block);
+			uint8 dbits = _automapDoorBits.contains(bkey) ? _automapDoorBits[bkey] : 0;
+			if ((_wllWallFlags[bp->walls[0]] & 8) || (_wllWallFlags[bp->walls[2]] & 8))
+				dbits |= 1;
+			if ((_wllWallFlags[bp->walls[1]] & 8) || (_wllWallFlags[bp->walls[3]] & 8))
+				dbits |= 2;
+			if (dbits)
+				_automapDoorBits[bkey] = dbits;
+			const bool doorNS = (dbits & 1) != 0;
+			const bool doorEW = (dbits & 2) != 0;
+			if (doorNS)
+				wall[0] = wall[2] = false;
+			if (doorEW)
+				wall[1] = wall[3] = false;
+
+			surf.fillRect(Common::Rect(sx, sy, sx + cell, sy + cell), visited ? cFloor : cFloorSeen);
+			if (visited) {
+				surf.hLine(sx, sy, sx + cell - 1, cGrid);
+				surf.vLine(sx, sy, sy + cell - 1, cGrid);
+			}
+			const uint32 wc = visited ? cWall : cWallSeen;
+			if (wall[0])
+				surf.fillRect(Common::Rect(sx, sy, sx + cell, sy + wt), wc);
+			if (wall[1])
+				surf.fillRect(Common::Rect(sx + cell - wt, sy, sx + cell, sy + cell), wc);
+			if (wall[2])
+				surf.fillRect(Common::Rect(sx, sy + cell - wt, sx + cell, sy + cell), wc);
+			if (wall[3])
+				surf.fillRect(Common::Rect(sx, sy, sx + wt, sy + cell), wc);
+
+			if (doorNS || doorEW) {
+				const int dt = MAX(2, wt + 1);
+				const int dm = MAX(1, cell / 5);
+				const int dcx = sx + cell / 2, dcy = sy + cell / 2;
+				if (doorNS)
+					surf.fillRect(Common::Rect(sx + dm, dcy - dt / 2, sx + cell - dm, dcy - dt / 2 + dt), cDoor);
+				if (doorEW)
+					surf.fillRect(Common::Rect(dcx - dt / 2, sy + dm, dcx - dt / 2 + dt, sy + cell - dm), cDoor);
+			}
+
+			// Special-wall pip on the carrying side (skip door types 5/6; 10 = niche).
+			for (int d = 0; d < 4; ++d) {
+				const uint8 st = vm->_specialWallTypes[bp->walls[d]];
+				if (st == 0 || st == 5 || st == 6)
+					continue;
+				const uint32 pcol = (st == 10) ? cNiche : cLever;
+				const int ps = MAX(2, cell / 4);
+				int px, py;
+				switch (d) {
+				case 0:
+					px = sx + cell / 2 - ps / 2;
+					py = sy;
+					break;
+				case 1:
+					px = sx + cell - ps;
+					py = sy + cell / 2 - ps / 2;
+					break;
+				case 2:
+					px = sx + cell / 2 - ps / 2;
+					py = sy + cell - ps;
+					break;
+				default:
+					px = sx;
+					py = sy + cell / 2 - ps / 2;
+					break;
+				}
+				surf.fillRect(Common::Rect(px, py, px + ps, py + ps), pcol);
+			}
+
+			if (_automapIcons.contains(bkey)) {
+				const uint8 ic = _automapIcons[bkey];
+				uint32 col = (ic == kAmTeleport) ? cTele : cStair;
+				if (!visited)
+					col = cWallSeen;
+				drawIcon(&surf, sx, sy, cell, ic, col);
+			}
+
+			if (_automapNotes.contains(bkey)) {
+				const int ds = MAX(2, cell / 3);
+				surf.fillRect(Common::Rect(sx + cell - ds, sy, sx + cell, sy + ds), cNote);
+			}
+		}
+	}
+
+	if (_selectedBlock != 0xFFFF) {
+		const int sx = offX + (_selectedBlock & 0x1F) * cell;
+		const int sy = offY + (_selectedBlock >> 5) * cell;
+		surf.frameRect(Common::Rect(sx, sy, sx + cell, sy + cell), cSel);
+		surf.frameRect(Common::Rect(sx + 1, sy + 1, sx + cell - 1, sy + cell - 1), cSel);
+	}
+
+	const int mx = offX + (vm->_currentBlock & 0x1F) * cell;
+	const int my = offY + (vm->_currentBlock >> 5) * cell;
+	const int cx = mx + cell / 2;
+	const int cyp = my + cell / 2;
+	const int r = MAX(2, cell / 2 - MAX(1, cell / 6));
+	const int b = MAX(2, r * 3 / 4);
+	int tipX, tipY, l1X, l1Y, l2X, l2Y;
+	switch (vm->_currentDirection) {
+	case 1:
+		tipX = cx + r;
+		tipY = cyp;
+		l1X = cx - b;
+		l1Y = cyp - b;
+		l2X = cx - b;
+		l2Y = cyp + b;
+		break;
+	case 2:
+		tipX = cx;
+		tipY = cyp + r;
+		l1X = cx - b;
+		l1Y = cyp - b;
+		l2X = cx + b;
+		l2Y = cyp - b;
+		break;
+	case 3:
+		tipX = cx - r;
+		tipY = cyp;
+		l1X = cx + b;
+		l1Y = cyp - b;
+		l2X = cx + b;
+		l2Y = cyp + b;
+		break;
+	default:
+		tipX = cx;
+		tipY = cyp - r;
+		l1X = cx - b;
+		l1Y = cyp + b;
+		l2X = cx + b;
+		l2Y = cyp + b;
+		break;
+	}
+	automapFillTri(surf, tipX, tipY, l1X, l1Y, l2X, l2Y, cPartyEdge);
+	const int gx = (tipX + l1X + l2X) / 3, gy = (tipY + l1Y + l2Y) / 3;
+	automapFillTri(surf,
+				   tipX + (gx - tipX) / 4, tipY + (gy - tipY) / 4,
+				   l1X + (gx - l1X) / 4, l1Y + (gy - l1Y) / 4,
+				   l2X + (gx - l2X) / 4, l2Y + (gy - l2Y) / 4, cParty);
+
+	// Plaque text: area name over coords, in two size-fitted bands so they never overlap.
+	if (bigFont) {
+		const uint16 cb = (_selectedBlock != 0xFFFF) ? _selectedBlock : vm->_currentBlock;
+		const Common::String lvl = _levelNames[vm->_currentLevel - 1];
+		const Common::String crd = Common::String::format("X %d   Y %d", cb & 0x1F, cb >> 5);
+		const int pm = MAX(3, sc * 2);
+		const int innerW = L.plW - 2 * pm;
+		const int innerH = L.plH - 2 * pm;
+		const int gap = MAX(2, innerH / 12);
+		const int lvlBand = (innerH - gap) * 6 / 10; // level gets ~60%, coords ~40%
+		const int crdBand = (innerH - gap) - lvlBand;
+		const int lvlSc = automapFit(bigFont, lvl, innerW, MAX(1, lvlBand / fh));
+		const int crdSc = automapFit(bigFont, crd, innerW, MAX(1, crdBand / fh));
+		const int lvlY = L.plY + pm + (lvlBand - fh * lvlSc) / 2;
+		const int crdY = L.plY + pm + lvlBand + gap + (crdBand - fh * crdSc) / 2;
+		automapDrawBigString(surf, bigFont, lvl, L.plX, lvlY, L.plW, cGold, lvlSc);
+		automapDrawBigString(surf, bigFont, crd, L.plX, crdY, L.plW, cPanelTxt, crdSc);
+	}
+
+	// Footer: the selected cell's note / auto-info (the KEYS panel documents N).
+	if (bigFont) {
+		Common::String foot;
+		if (_editing) {
+			const char *caret = ((_system->getMillis() / 400) & 1) ? "_" : " ";
+			foot = Common::String("Note: ") + _editBuffer + caret;
+		} else if (_selectedBlock != 0xFFFF) {
+			const uint32 selKey = noteKey(vm->_currentLevel, _selectedBlock);
+			Common::String body;
+			if (_automapNotes.contains(selKey)) {
+				body = _automapNotes[selKey];
+			} else {
+				if (_automapAutoInfo.contains(selKey))
+					body = _automapAutoInfo[selKey];
+				const Common::String items = listItems(vm, _selectedBlock);
+				if (!items.empty())
+					body = body.empty() ? items : body + " - " + items;
+			}
+			if (!body.empty())
+				foot = Common::String("Selected: ") + body;
+		}
+		if (!foot.empty()) {
+			const int footH = (L.mapY + L.mapH) - L.footY;
+			const int fpad = MAX(2, footH / 6);
+			const int maxFsc = MAX(1, (footH - 2 * fpad) / fh);
+			const int fw = L.mapW - 2 * mpad;
+			const int fsc = MIN(automapFit(bigFont, foot, fw, sc), maxFsc);
+			const int fy = L.footY + (footH - fh * fsc) / 2;
+			automapDrawBigString(surf, bigFont, foot, L.mapX + mpad, fy, fw, cInk, fsc, Graphics::kTextAlignLeft);
+		}
+	}
+
+	_system->copyRectToOverlay(surf.getPixels(), surf.pitch, 0, 0, ow, oh);
+	_system->updateScreen();
+}
+
+void Automap_EoB::saveStrings(Common::OutSaveFile *out) const {
+	auto writeStringMap = [&out](const Common::HashMap<uint32, Common::String> &map) {
+		out->writeUint32BE(map.size());
+		for (Common::HashMap<uint32, Common::String>::const_iterator it = map.begin(); it != map.end(); ++it) {
+			out->writeUint32BE(it->_key);
+			out->writeString(it->_value);
+			out->writeByte(0);
+		}
+	};
+
+	writeStringMap(_automapNotes);
+	out->writeUint32BE(_automapIcons.size());
+	for (Common::HashMap<uint32, uint8>::const_iterator it = _automapIcons.begin(); it != _automapIcons.end(); ++it) {
+		out->writeUint32BE(it->_key);
+		out->writeByte(it->_value);
+	}
+	writeStringMap(_automapAutoInfo);
+}
+
+void Automap_EoB::loadStrings(Common::SeekableReadStreamEndianWrapper *in) {
+	_automapNotes.clear();
+	_automapIcons.clear();
+	_automapAutoInfo.clear();
+	_automapDoorBits.clear();
+
+	auto readStringMap = [&in](Common::HashMap<uint32, Common::String> &map) {
+		uint32 n = in->readUint32BE();
+		for (uint32 i = 0; i < n; ++i) {
+			uint32 k = in->readUint32BE();
+			map[k] = in->readString();
+		}
+	};
+
+	readStringMap(_automapNotes);
+	uint32 numIcons = in->readUint32BE();
+	for (uint32 i = 0; i < numIcons; ++i) {
+		uint32 k = in->readUint32BE();
+		_automapIcons[k] = in->readByte();
+	}
+	readStringMap(_automapAutoInfo);
+}
+
+Automap_EoB::AutomapLayout Automap_EoB::createLayout() const {
+	const int ow = _system->getOverlayWidth();
+	const int oh = _system->getOverlayHeight();
+	AutomapLayout l;
+
+	// Stone frame around the overlay, a side panel on the right, and a parchment map
+	// inset filling the rest; the 32x32 grid is centred with a footer strip below.
+	l.frame = CLIP<int>(oh / 36, 8, 28);
+	const int gap = MAX(4, l.frame / 2);
+	l.sideW = MAX(150, ow / 4);
+
+	const int inX = l.frame, inY = l.frame;
+	const int inW = ow - 2 * l.frame, inH = oh - 2 * l.frame;
+
+	l.mapX = inX;
+	l.mapY = inY;
+	l.mapW = inW - l.sideW - gap;
+	l.mapH = inH;
+	l.sideX = inX + l.mapW + gap;
+	l.sideY = inY;
+	l.sideH = inH;
+
+	const int mpad = MAX(8, l.mapW / 40);
+	const int footStrip = MAX(18, oh / 22);
+	const int availW = l.mapW - 2 * mpad;
+	const int availH = l.mapH - 2 * mpad - footStrip;
+	l.cell = MAX(2, MIN(availW / 32, availH / 32));
+	const int grid = l.cell * 32;
+	l.offX = l.mapX + (l.mapW - grid) / 2;
+	l.offY = l.mapY + mpad + (availH - grid) / 2;
+	l.footY = l.mapY + l.mapH - footStrip;
+
+	const int spad = MAX(6, l.sideW / 12);
+	l.plX = l.sideX + spad;
+	l.plY = l.sideY + spad;
+	l.plW = l.sideW - 2 * spad;
+	l.plH = MAX(30, oh / 8);
+	return l;
+}
+
+void Automap_EoB::moveSelection(int dx, int dy) {
+	const int bx = CLIP<int>((_selectedBlock & 0x1F) + dx, 0, 31);
+	const int by = CLIP<int>((_selectedBlock >> 5) + dy, 0, 31);
+	_selectedBlock = (by << 5) | bx;
+}
+
+void Automap_EoB::handleClick(EoBCoreEngine *vm) {
+	const Common::Point p = vm->_eventMan->getMousePos();
+	const AutomapLayout l = createLayout();
+	if (p.x < l.offX || p.y < l.offY)
+		return;
+	const int bx = (p.x - l.offX) / l.cell;
+	const int by = (p.y - l.offY) / l.cell;
+	if (bx < 0 || bx >= 32 || by < 0 || by >= 32)
+		return;
+	const uint16 block = (by << 5) | bx;
+	if (isVisited(block) || isSeen(block))
+		_selectedBlock = block;
+}
+
+void Automap_EoB::editNote(EoBCoreEngine *vm) {
+	uint16 block = _selectedBlock;
+	if (!(isVisited(block) || isSeen(block)))
+		return;
+	_selectedBlock = block;
+
+	Common::Keymap *km = vm->_eventMan->getKeymapper()->getKeymap(vm->kKeymapName);
+	km->setEnabled(false);
+
+	const uint32 key = noteKey(vm->_currentLevel, block);
+	_editBuffer = _automapNotes.contains(key) ? _automapNotes[key] : Common::String();
+	_editing = true;
+
+	const uint kMaxLen = 40;
+	bool done = false, cancel = false;
+	while (!done && !vm->shouldQuit()) {
+		draw(vm);
+		vm->updateInput();
+		for (Common::List<KyraEngine_v1::Event>::const_iterator e = vm->_eventList.begin(); e != vm->_eventList.end(); ++e) {
+			if (e->event.type != Common::EVENT_KEYDOWN)
+				continue;
+			const Common::KeyCode kc = e->event.kbd.keycode;
+			const uint16 asc = e->event.kbd.ascii;
+			if (kc == Common::KEYCODE_RETURN || kc == Common::KEYCODE_KP_ENTER)
+				done = true;
+			else if (kc == Common::KEYCODE_ESCAPE)
+				done = cancel = true;
+			else if (kc == Common::KEYCODE_BACKSPACE) {
+				if (!_editBuffer.empty())
+					_editBuffer.deleteLastChar();
+			} else if (asc >= 32 && asc < 127 && _editBuffer.size() < kMaxLen) {
+				_editBuffer += (char)asc;
+			}
+		}
+		vm->_eventList.clear();
+		_system->delayMillis(12);
+	}
+
+	if (!cancel) {
+		_editBuffer.trim();
+		if (_editBuffer.empty())
+			_automapNotes.erase(key);
+		else
+			_automapNotes[key] = _editBuffer;
+	}
+
+	_editing = false;
+	_editBuffer.clear();
+	km->setEnabled(true);
+}
+
+Common::String Automap_EoB::listItems(EoBCoreEngine *vm, uint16 block) const {
+	if (block >= 1024)
+		return Common::String();
+	Common::String out;
+	const uint16 head = _blockData[block].drawObjects;
+	uint16 o = head;
+	int guard = 0;
+	while (o && guard++ < 64) {
+		const EoBItem *it = &vm->_items[o];
+		const char *nm = vm->_itemNames[it->nameUnid];
+		if (nm && *nm) {
+			if (!out.empty())
+				out += ", ";
+			out += nm;
+		}
+		o = it->next;
+		if (o == head)
+			break;
+	}
+	return out;
+}
+
+uint16 Automap_EoB::calcNewBlockPosition(uint16 block, int8 dir) const {
+	static const int16 blockPosTable[] = {-32, 1, 32, -1};
+	return (block + blockPosTable[dir & 3]) & 0x3FF;
+}
+
+static void automapFillTri(Graphics::Surface &s, int ax, int ay, int bx, int by, int cx, int cy, uint32 color);
+
+// Comma-separated names of the items currently on `block`. "" if none.
+
+
+void Automap_EoB::drawIcon(Graphics::Surface *surf, int sx, int sy, int cell, uint8 icon, uint32 color) const {
+	const int cx = sx + cell / 2;
+	const int cy = sy + cell / 2;
+	const int r = MAX(2, cell / 3);
+	switch (icon) {
+	case kAmStairsDown:
+		automapFillTri(*surf, cx - r, cy - r, cx + r, cy - r, cx, cy + r, color);
+		break;
+	case kAmStairsUp:
+		automapFillTri(*surf, cx - r, cy + r, cx + r, cy + r, cx, cy - r, color);
+		break;
+	case kAmTeleport:
+		surf->frameRect(Common::Rect(cx - r, cy - r, cx + r, cy + r), color);
+		surf->frameRect(Common::Rect(cx - r + 1, cy - r + 1, cx + r - 1, cy + r - 1), color);
+		break;
+	default:
+		break;
+	}
+}
+
+} // End of namespace Kyra
+
+#endif // ENABLE_EOB
diff --git a/engines/kyra/gui/automap_eob.h b/engines/kyra/gui/automap_eob.h
new file mode 100644
index 00000000000..bd1e94fb0c9
--- /dev/null
+++ b/engines/kyra/gui/automap_eob.h
@@ -0,0 +1,133 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef KYRA_GUI_AUTOMAP_EOB_H
+#define KYRA_GUI_AUTOMAP_EOB_H
+
+#if defined(ENABLE_EOB) || defined(ENABLE_LOL)
+
+#include "common/scummsys.h"
+#include "common/hashmap.h" // TODO: REMOVE
+#include "common/str.h" // TODO: REMOVE
+
+namespace Common {
+class OutSaveFile;
+class SeekableReadStreamEndianWrapper;
+} // End of namespace Common
+
+namespace Graphics {
+struct Surface;
+} // End of namespace Graphics
+
+class OSystem;
+
+namespace Kyra {
+
+struct LevelBlockProperty;
+class EoBCoreEngine; // TODO: REMOVE
+
+class Automap_EoB {
+public:
+	Automap_EoB(OSystem *system, LevelBlockProperty **blockData, const uint8 *wllFlags, int gameID, int lang, bool featureEnabled);
+	~Automap_EoB();
+
+	void markVisited(uint16 block);
+	bool isVisited(uint16 block) const;
+	void markSeen(uint16 block, int8 dir);
+	bool isSeen(uint16 block) const;
+
+	void tagTransition(int fromLevel, uint16 fromBlock, int toLevel); // TODO: REMOVE
+	void collectCellInfo(int level, uint16 block, int teleporterWallId); // TODO: REMOVE
+
+	void mainLoopProcess(EoBCoreEngine *vm, int inputFlag); // TODO: REMOVE
+	bool isVisible() const { return _visible; } // TODO: REMOVE
+	void draw(EoBCoreEngine *vm);
+
+	void saveStrings(Common::OutSaveFile *out) const;
+	void loadStrings(Common::SeekableReadStreamEndianWrapper *in);
+
+private:
+	// Geometry shared by drawing and click hit-testing.
+	struct AutomapLayout {
+		int cell;
+		int offX, offY;                 // top-left of the 32x32 grid (mouse hit-test)
+		int frame;                      // stone frame thickness
+		int mapX, mapY, mapW, mapH;     // parchment region (map + footer strip)
+		int sideX, sideY, sideW, sideH; // stone side panel (plaque, legend, keys)
+		int plX, plY, plW, plH;         // level/coords plaque inside the side panel
+		int footY;                      // top of the selected-note footer strip
+	};
+
+	struct TranslateableStrings {
+		const char *const legendStrings[9];
+		const char *const levelNames[2][16];
+	};
+
+	AutomapLayout createLayout() const;
+
+	uint32 noteKey(uint lvl, uint16 block) const { return (lvl << 16) | block; }
+
+	void moveSelection(int dx, int dy);
+	void handleClick(EoBCoreEngine *vm);
+	void editNote(EoBCoreEngine *vm);
+
+	Common::String listItems(EoBCoreEngine *vm, uint16 block) const; // TODO: remove
+
+	void drawIcon(Graphics::Surface *surf, int sx, int sy, int cell, uint8 icon, uint32 color) const;
+
+	uint16 calcNewBlockPosition(uint16 block, int8 dir) const;
+
+	bool _visible;
+
+	LevelBlockProperty *&_blockData;
+	const uint8 *const _wllWallFlags;
+	const bool _enabled;
+
+	enum AutomapIcon {
+		kAmNone = 0,
+		kAmStairsDown = 1,
+		kAmStairsUp = 2,
+		kAmTeleport = 3
+	};
+
+	bool _editing;                    // TODO: remove
+	Common::String _editBuffer;            // TODO: remove
+	uint16 _selectedBlock; // TODO: remove
+	Common::HashMap<uint32, uint8> _automapIcons; // TODO: remove
+	Common::HashMap<uint32, Common::String> _automapAutoInfo; // TODO: remove
+	Common::HashMap<uint32, uint8> _automapDoorBits; // TODO: remove
+	Common::HashMap<uint32, Common::String> _automapNotes; // TODO: remove
+
+	static const TranslateableStrings _stringTable[];
+	const char * const *_legendStrings;
+	const char * const *_levelNames;
+	const int _numLevelNames;
+
+	OSystem *_system;
+	Graphics::Surface *_automapBg;
+	Graphics::Surface *_automapFrame;
+};
+
+} // End of namespace Kyra
+
+#endif // ENABLE_EOB || ENABLE_LOL
+
+#endif // KYRA_GUI_AUTOMAP_EOB_H
diff --git a/engines/kyra/gui/gui_eob.cpp b/engines/kyra/gui/gui_eob.cpp
index ffc1aec4303..0f214800119 100644
--- a/engines/kyra/gui/gui_eob.cpp
+++ b/engines/kyra/gui/gui_eob.cpp
@@ -4974,694 +4974,6 @@ const uint8 GUI_EoB::_highlightColorTablePC98[] = { 0x0C, 0x0D, 0x0E, 0x0F, 0x0E
 
 const uint8 GUI_EoB::_highlightColorTableSegaCD[] = { 0x3D, 0x3D, 0x3D, 0x3E, 0x3E, 0x3E, 0x3F, 0x3F, 0x3F, 0x3E, 0x3E, 0x3E, 0x00 };
 
-// Automap (non-original): a north-up map of the 32x32 level grid (block = (y<<5)|x),
-// drawn over the 3D view and toggled with Tab.
-
-void EoBCoreEngine::automapMarkSeenFromCurrent() {
-	// Mark the cells in the line of sight ahead (not side cells), up to the first wall.
-	const int dir = _currentDirection;
-	uint16 block = _currentBlock;
-	for (int depth = 1; depth <= 2; ++depth) {
-		// Passability via the neighbour's facing wall (walls[dir ^ 2]), as movement uses.
-		const uint16 nb = calcNewBlockPosition(block, dir);
-		if (!(_wllWallFlags[_levelBlockProperties[nb].walls[dir ^ 2]] & 1))
-			break;
-		block = nb;
-		automapMarkSeen(block);
-	}
-}
-
-static void automapFillTri(Graphics::Surface &s, int ax, int ay, int bx, int by, int cx, int cy, uint32 color);
-
-// Comma-separated names of the items currently on `block`. "" if none.
-Common::String EoBCoreEngine::automapLiveItems(uint16 block) const {
-	if (block >= 1024)
-		return Common::String();
-	Common::String out;
-	const uint16 head = _levelBlockProperties[block].drawObjects;
-	uint16 o = head;
-	int guard = 0;
-	while (o && guard++ < 64) {
-		const EoBItem *it = &_items[o];
-		const char *nm = _itemNames[it->nameUnid];
-		if (nm && *nm) {
-			if (!out.empty())
-				out += ", ";
-			out += nm;
-		}
-		o = it->next;
-		if (o == head) // the floor list is circular
-			break;
-	}
-	return out;
-}
-
-// On arrival, flag a teleporter pad. Stairs/destinations are learned in automapTagTransition.
-void EoBCoreEngine::automapCollectCellInfo(uint16 block) {
-	if (_currentLevel >= 20 || block >= 1024)
-		return;
-	const uint32 k = automapNoteKey(block);
-	if (_automapIcons.contains(k)) // already classified (e.g. as stairs)
-		return;
-	for (int d = 0; d < 4; ++d) {
-		if (_levelBlockProperties[block].walls[d] == _teleporterWallId) {
-			_automapIcons[k] = kAmTeleport;
-			if (!_automapAutoInfo.contains(k))
-				_automapAutoInfo[k] = "Teleporter";
-			break;
-		}
-	}
-}
-
-// On a script-driven level change, tag the cell we left with a stairs/teleport
-// glyph and a "to level N" note (deeper level = down).
-void EoBCoreEngine::automapTagTransition(int fromLevel, uint16 fromBlock, int toLevel) {
-	if (fromLevel < 0 || fromLevel >= 20 || fromBlock >= 1024)
-		return;
-	const uint32 k = ((uint32)fromLevel << 16) | fromBlock;
-	uint8 icon;
-	Common::String info;
-	if (toLevel > fromLevel) {
-		icon = kAmStairsDown;
-		info = Common::String::format("Down to level %d", toLevel);
-	} else if (toLevel < fromLevel) {
-		icon = kAmStairsUp;
-		info = Common::String::format("Up to level %d", toLevel);
-	} else {
-		icon = kAmTeleport;
-		info = Common::String::format("Teleport to level %d", toLevel);
-	}
-	_automapIcons[k] = icon;
-	_automapAutoInfo[k] = info;
-}
-
-void EoBCoreEngine::automapDrawIcon(Graphics::Surface &surf, int sx, int sy, int cell, uint8 icon, uint32 color) const {
-	const int cx = sx + cell / 2;
-	const int cy = sy + cell / 2;
-	const int r = MAX(2, cell / 3);
-	switch (icon) {
-	case kAmStairsDown:
-		automapFillTri(surf, cx - r, cy - r, cx + r, cy - r, cx, cy + r, color);
-		break;
-	case kAmStairsUp:
-		automapFillTri(surf, cx - r, cy + r, cx + r, cy + r, cx, cy - r, color);
-		break;
-	case kAmTeleport:
-		surf.frameRect(Common::Rect(cx - r, cy - r, cx + r, cy + r), color);
-		surf.frameRect(Common::Rect(cx - r + 1, cy - r + 1, cx + r - 1, cy + r - 1), color);
-		break;
-	default:
-		break;
-	}
-}
-
-// Scaled text: render at native font size, then nearest-neighbour upscale.
-static void automapDrawBigString(Graphics::Surface &dst, const Graphics::Font *font,
-		const Common::String &text, int x, int y, int w, uint32 color, int scale,
-		Graphics::TextAlign align = Graphics::kTextAlignCenter) {
-	if (!font || text.empty() || scale < 1)
-		return;
-	const int tw = font->getStringWidth(text);
-	const int th = font->getFontHeight();
-	if (tw <= 0 || th <= 0)
-		return;
-
-	if (scale == 1) {
-		font->drawString(&dst, text, x, y, w, color, align);
-		return;
-	}
-
-	// The overlay may be 16- or 32-bit; index pixels by byte width.
-	const int bpp = dst.format.bytesPerPixel;
-	if (bpp != 2 && bpp != 4) { // unexpected depth: fall back to native size
-		font->drawString(&dst, text, x, y, w, color, align);
-		return;
-	}
-
-	// Render onto a zeroed scratch surface so inked pixels are distinguishable.
-	Graphics::Surface tmp;
-	tmp.create(tw, th, dst.format);
-	tmp.fillRect(Common::Rect(0, 0, tw, th), 0);
-	font->drawString(&tmp, text, 0, 0, tw, color, Graphics::kTextAlignLeft);
-
-	const int dw = tw * scale;
-	int dx = x + (w - dw) / 2; // center (default)
-	if (align == Graphics::kTextAlignLeft)
-		dx = x;
-	else if (align == Graphics::kTextAlignRight)
-		dx = x + w - dw;
-	if (dx < x)
-		dx = x;
-	for (int sy = 0; sy < th; ++sy) {
-		const byte *src = (const byte *)tmp.getBasePtr(0, sy);
-		for (int sx = 0; sx < tw; ++sx) {
-			const uint32 c = (bpp == 2) ? ((const uint16 *)src)[sx] : ((const uint32 *)src)[sx];
-			if (c == 0) // transparent background pixel
-				continue;
-			const int px = dx + sx * scale;
-			const int py = y + sy * scale;
-			dst.fillRect(Common::Rect(px, py, px + scale, py + scale), c);
-		}
-	}
-	tmp.free();
-}
-
-void EoBCoreEngine::automapToggle() {
-	_automapVisible = !_automapVisible;
-	if (_automapVisible) {
-		_automapSelectedBlock = _currentBlock;
-		// inGUI=true => getMousePos() is in overlay coords, matching the drawn grid.
-		_system->showOverlay(true);
-	} else {
-		_system->hideOverlay();
-		_sceneUpdateRequired = true;
-	}
-}
-
-// Solid triangle by scanlines (party arrow).
-static void automapFillTri(Graphics::Surface &s, int ax, int ay, int bx, int by, int cx, int cy, uint32 color) {
-	// Sort vertices by y.
-	if (ay > by) { SWAP(ax, bx); SWAP(ay, by); }
-	if (ay > cy) { SWAP(ax, cx); SWAP(ay, cy); }
-	if (by > cy) { SWAP(bx, cx); SWAP(by, cy); }
-	if (cy == ay)
-		return;
-
-	for (int y = ay; y <= cy; ++y) {
-		int xLong = ax + (cx - ax) * (y - ay) / (cy - ay);
-		int xShort = (y < by)
-			? (by == ay ? ax : ax + (bx - ax) * (y - ay) / (by - ay))
-			: (cy == by ? bx : bx + (cx - bx) * (y - by) / (cy - by));
-		int x0 = MIN(xLong, xShort);
-		int x1 = MAX(xLong, xShort);
-		s.hLine(x0, y, x1, color);
-	}
-}
-
-// Geometry shared by drawing and click hit-testing (so clicks hit the drawn cell).
-EoBCoreEngine::AutomapLayout EoBCoreEngine::automapLayout() const {
-	const int ow = _system->getOverlayWidth();
-	const int oh = _system->getOverlayHeight();
-	AutomapLayout l;
-
-	// Stone frame around the overlay, a side panel on the right, and a parchment map
-	// inset filling the rest; the 32x32 grid is centred with a footer strip below.
-	l.frame = CLIP<int>(oh / 36, 8, 28);
-	const int gap = MAX(4, l.frame / 2);
-	l.sideW = MAX(150, ow / 4);
-
-	const int inX = l.frame, inY = l.frame;
-	const int inW = ow - 2 * l.frame, inH = oh - 2 * l.frame;
-
-	l.mapX = inX;
-	l.mapY = inY;
-	l.mapW = inW - l.sideW - gap;
-	l.mapH = inH;
-	l.sideX = inX + l.mapW + gap;
-	l.sideY = inY;
-	l.sideH = inH;
-
-	const int mpad = MAX(8, l.mapW / 40);
-	const int footStrip = MAX(18, oh / 22);
-	const int availW = l.mapW - 2 * mpad;
-	const int availH = l.mapH - 2 * mpad - footStrip;
-	l.cell = MAX(2, MIN(availW / 32, availH / 32));
-	const int grid = l.cell * 32;
-	l.offX = l.mapX + (l.mapW - grid) / 2;
-	l.offY = l.mapY + mpad + (availH - grid) / 2;
-	l.footY = l.mapY + l.mapH - footStrip;
-
-	const int spad = MAX(6, l.sideW / 12);
-	l.plX = l.sideX + spad;
-	l.plY = l.sideY + spad;
-	l.plW = l.sideW - 2 * spad;
-	l.plH = MAX(30, oh / 8);
-	return l;
-}
-
-// Area name for the plaque, indexed by internal level number (EOB has no names in
-// its data). EOB2's numbering is offset (the forest is level 4); the names were
-// derived from each LEVELn.INF's wall-set/palette, not walkthrough play-order.
-Common::String EoBCoreEngine::automapLevelName() const {
-	static const char *const eob1[12] = {
-		"Upper Sewers", "Middle Sewers", "Lower Sewers",
-		"Dwarven Ruins I", "Dwarven Camp", "Dwarven Ruins III",
-		"Upper Drow", "Drow Outcasts", "Lower Drow",
-		"Mantis Hive", "Xanathar Sanctum", "Xanathar's Lair"
-	};
-	static const char *const eob2[16] = {
-		"Catacombs I", "Catacombs II", "Catacombs III", "Forest",
-		"Darkmoon Temple", "Temple Mezzanine", "Silver Tower I", "Silver Tower II",
-		"Silver Tower III", "Azure Tower I", "Azure Tower II", "Azure Tower III",
-		"Azure Tower IV", "Azure Tower V", "Crimson Tower I", "Crimson Tower II"
-	};
-	const int idx = _currentLevel - 1;
-	const char *name = nullptr;
-	if (_flags.gameID == GI_EOB1) {
-		if (idx >= 0 && idx < 12)
-			name = eob1[idx];
-	} else if (_flags.gameID == GI_EOB2) {
-		if (idx >= 0 && idx < 16)
-			name = eob2[idx];
-	}
-	if (name)
-		return Common::String(name);
-	return Common::String::format("Level %d", _currentLevel);
-}
-
-// Move the selection cursor (Shift+arrows), clamped to the grid.
-void EoBCoreEngine::automapMoveSelection(int dx, int dy) {
-	const uint16 cur = (_automapSelectedBlock != 0xFFFF) ? _automapSelectedBlock : _currentBlock;
-	const int bx = CLIP<int>((cur & 0x1F) + dx, 0, 31);
-	const int by = CLIP<int>((cur >> 5) + dy, 0, 31);
-	_automapSelectedBlock = (by << 5) | bx;
-}
-
-// A click selects the cell (editing is a separate step, N), so the map can be browsed.
-void EoBCoreEngine::automapHandleClick() {
-	const Common::Point p = _eventMan->getMousePos();
-	const AutomapLayout l = automapLayout();
-	if (p.x < l.offX || p.y < l.offY)
-		return;
-	const int bx = (p.x - l.offX) / l.cell;
-	const int by = (p.y - l.offY) / l.cell;
-	if (bx < 0 || bx >= 32 || by < 0 || by >= 32)
-		return;
-	const uint16 block = (by << 5) | bx;
-	if (automapIsVisited(block) || automapIsSeen(block))
-		_automapSelectedBlock = block;
-}
-
-// Note editor for the selected cell. Disables the game keymap so letters arrive as
-// raw ASCII (as GUI_EoB::getTextInput does); Enter saves (empty deletes), Esc cancels.
-void EoBCoreEngine::automapEditNote() {
-	uint16 block = (_automapSelectedBlock != 0xFFFF) ? _automapSelectedBlock : _currentBlock;
-	if (!(automapIsVisited(block) || automapIsSeen(block)))
-		return;
-	_automapSelectedBlock = block;
-
-	Common::Keymap *km = _eventMan->getKeymapper()->getKeymap(kKeymapName);
-	km->setEnabled(false);
-
-	const uint32 key = automapNoteKey(block);
-	_automapEditBuffer = _automapNotes.contains(key) ? _automapNotes[key] : Common::String();
-	_automapEditing = true;
-
-	const uint kMaxLen = 40;
-	bool done = false, cancel = false;
-	while (!done && !shouldQuit()) {
-		automapDraw();
-		updateInput();
-		for (Common::List<KyraEngine_v1::Event>::const_iterator e = _eventList.begin(); e != _eventList.end(); ++e) {
-			if (e->event.type != Common::EVENT_KEYDOWN)
-				continue;
-			const Common::KeyCode kc = e->event.kbd.keycode;
-			const uint16 asc = e->event.kbd.ascii;
-			if (kc == Common::KEYCODE_RETURN || kc == Common::KEYCODE_KP_ENTER)
-				done = true;
-			else if (kc == Common::KEYCODE_ESCAPE)
-				done = cancel = true;
-			else if (kc == Common::KEYCODE_BACKSPACE) {
-				if (!_automapEditBuffer.empty())
-					_automapEditBuffer.deleteLastChar();
-			} else if (asc >= 32 && asc < 127 && _automapEditBuffer.size() < kMaxLen) {
-				_automapEditBuffer += (char)asc;
-			}
-		}
-		_eventList.clear();
-		_system->delayMillis(12);
-	}
-
-	if (!cancel) {
-		_automapEditBuffer.trim();
-		if (_automapEditBuffer.empty())
-			_automapNotes.erase(key);
-		else
-			_automapNotes[key] = _automapEditBuffer;
-	}
-
-	_automapEditing = false;
-	_automapEditBuffer.clear();
-	km->setEnabled(true);
-}
-
-// Sparse two-tone speckle to fake stone/paper grain (overwrites pixels, no blend).
-static void automapNoise(Graphics::Surface &s, int x0, int y0, int w, int h, uint32 cA, uint32 cB, uint32 seed) {
-	if (w <= 0 || h <= 0 || x0 < 0 || y0 < 0 || x0 + w > s.w || y0 + h > s.h)
-		return;
-	const int bpp = s.format.bytesPerPixel;
-	if (bpp != 2 && bpp != 4)
-		return;
-	uint32 st = seed | 1u;
-	for (int y = 0; y < h; ++y) {
-		byte *row = (byte *)s.getBasePtr(x0, y0 + y);
-		for (int x = 0; x < w; ++x) {
-			st = st * 1664525u + 1013904223u;
-			const uint32 v = st >> 24; // 0..255
-			if (v < 26) {
-				const uint32 c = (v & 1u) ? cA : cB;
-				if (bpp == 2)
-					((uint16 *)row)[x] = (uint16)c;
-				else
-					((uint32 *)row)[x] = c;
-			}
-		}
-	}
-}
-
-static void automapBevel(Graphics::Surface &s, const Common::Rect &r, uint32 hi, uint32 lo) {
-	s.hLine(r.left, r.top, r.right - 1, hi);
-	s.vLine(r.left, r.top, r.bottom - 1, hi);
-	s.hLine(r.left, r.bottom - 1, r.right - 1, lo);
-	s.vLine(r.right - 1, r.top, r.bottom - 1, lo);
-}
-
-static void automapRivet(Graphics::Surface &s, int x, int y, int sz, uint32 dark, uint32 mid, uint32 hi) {
-	s.fillRect(Common::Rect(x - sz, y - sz, x + sz + 1, y + sz + 1), dark);
-	s.fillRect(Common::Rect(x - sz + 1, y - sz + 1, x + sz, y + sz), mid);
-	const int q = MAX(1, sz / 2);
-	s.fillRect(Common::Rect(x - sz + 1, y - sz + 1, x - sz + 1 + q, y - sz + 1 + q), hi);
-}
-
-static int automapFit(const Graphics::Font *f, const Common::String &str, int maxW, int maxSc) {
-	int sc = MAX(1, maxSc);
-	while (sc > 1 && f && f->getStringWidth(str) * sc > maxW)
-		--sc;
-	return sc;
-}
-
-void EoBCoreEngine::automapDraw() {
-	automapMarkVisited(_currentBlock);
-
-	const int ow = _system->getOverlayWidth();
-	const int oh = _system->getOverlayHeight();
-	const Graphics::PixelFormat fmt = _system->getOverlayFormat();
-	const AutomapLayout L = automapLayout();
-
-	// "Stone tablet" palette: opaque grey stone + aged parchment (names self-explain).
-	const uint32 cStone     = fmt.RGBToColor(0x54, 0x56, 0x5e);
-	const uint32 cStoneDark = fmt.RGBToColor(0x3c, 0x3e, 0x45);
-	const uint32 cStoneEdge = fmt.RGBToColor(0x23, 0x25, 0x2a);
-	const uint32 cStoneHi   = fmt.RGBToColor(0x6b, 0x6e, 0x78);
-	const uint32 cRivet     = fmt.RGBToColor(0xc2, 0xc5, 0xcd);
-	const uint32 cPaper     = fmt.RGBToColor(0xd6, 0xbf, 0x94);
-	const uint32 cPaperHi   = fmt.RGBToColor(0xe2, 0xcd, 0xa4);
-	const uint32 cPaperLo   = fmt.RGBToColor(0xc4, 0xab, 0x78);
-	const uint32 cPaperEdge = fmt.RGBToColor(0xa9, 0x8b, 0x56);
-	const uint32 cInk       = fmt.RGBToColor(0x3a, 0x2a, 0x18);
-	const uint32 cInkSoft   = fmt.RGBToColor(0x7a, 0x60, 0x38);
-	const uint32 cFloor     = fmt.RGBToColor(0xbb, 0x9c, 0x5e); // a touch darker than paper
-	const uint32 cFloorSeen = fmt.RGBToColor(0xc7, 0xb1, 0x80);
-	const uint32 cGrid      = fmt.RGBToColor(0x8a, 0x70, 0x38);
-	const uint32 cWall      = fmt.RGBToColor(0x2c, 0x1e, 0x10);
-	const uint32 cWallSeen  = fmt.RGBToColor(0x8a, 0x73, 0x4a);
-	const uint32 cDoor      = fmt.RGBToColor(0x7a, 0x4a, 0x1c);
-	const uint32 cStair     = fmt.RGBToColor(0x3f, 0x7a, 0x3a);
-	const uint32 cTele      = fmt.RGBToColor(0x5a, 0x3f, 0x8a);
-	const uint32 cLever     = fmt.RGBToColor(0x9a, 0x2d, 0x2d);
-	const uint32 cNiche     = fmt.RGBToColor(0x8a, 0x5a, 0x1c);
-	const uint32 cNote      = fmt.RGBToColor(0x9a, 0x6a, 0x14);
-	const uint32 cParty     = fmt.RGBToColor(0xa8, 0x28, 0x1c);
-	const uint32 cPartyEdge = fmt.RGBToColor(0x5a, 0x14, 0x0e);
-	const uint32 cSel       = fmt.RGBToColor(0x7a, 0x2a, 0x18);
-	const uint32 cPlaqueBg  = fmt.RGBToColor(0x1c, 0x1a, 0x16);
-	const uint32 cPlaqueEd  = fmt.RGBToColor(0x0d, 0x0c, 0x0a);
-	const uint32 cGold      = fmt.RGBToColor(0xf0, 0xc8, 0x50);
-	const uint32 cGoldDim   = fmt.RGBToColor(0xb8, 0x92, 0x3a);
-	const uint32 cPanelTxt  = fmt.RGBToColor(0xd8, 0xc8, 0xa8);
-
-	const Graphics::Font *bigFont = FontMan.getFontByUsage(Graphics::FontManager::kBigGUIFont);
-	const int fh = bigFont ? bigFont->getFontHeight() : 8;
-	const int sc = CLIP<int>(oh / 320, 1, 3); // base side-panel text scale
-	const int mpad = MAX(8, L.mapW / 40);
-
-	// (Re)build the cached chrome on an overlay resize (free() zeroes w/h, so an
-	// empty surface never matches).
-	if (_automapBg.w != ow || _automapBg.h != oh) {
-		_automapBg.free();
-		_automapBg.create(ow, oh, fmt);
-		_automapFrame.free();
-		_automapFrame.create(ow, oh, fmt);
-		Graphics::Surface &bg = _automapBg;
-
-		bg.fillRect(Common::Rect(0, 0, ow, oh), cStone);
-		automapNoise(bg, 0, 0, ow, oh, cStoneHi, cStoneEdge, 0x9e3779b9u);
-		automapBevel(bg, Common::Rect(0, 0, ow, oh), cStoneHi, cStoneEdge);
-		automapBevel(bg, Common::Rect(2, 2, ow - 2, oh - 2), cStone, cStoneEdge);
-		const int rv = MAX(2, L.frame / 4);
-		const int ri = L.frame / 2;
-		automapRivet(bg, ri, ri, rv, cStoneEdge, cStoneHi, cRivet);
-		automapRivet(bg, ow - ri, ri, rv, cStoneEdge, cStoneHi, cRivet);
-		automapRivet(bg, ri, oh - ri, rv, cStoneEdge, cStoneHi, cRivet);
-		automapRivet(bg, ow - ri, oh - ri, rv, cStoneEdge, cStoneHi, cRivet);
-
-		const Common::Rect mr(L.mapX, L.mapY, L.mapX + L.mapW, L.mapY + L.mapH);
-		bg.fillRect(mr, cPaperEdge);
-		bg.fillRect(Common::Rect(mr.left + 2, mr.top + 2, mr.right - 2, mr.bottom - 2), cPaper);
-		automapNoise(bg, mr.left + 2, mr.top + 2, L.mapW - 4, L.mapH - 4, cPaperHi, cPaperLo, 0x85ebca6bu);
-		bg.frameRect(Common::Rect(mr.left + 2, mr.top + 2, mr.right - 2, mr.bottom - 2), cInkSoft);
-		bg.frameRect(Common::Rect(mr.left + 5, mr.top + 5, mr.right - 5, mr.bottom - 5), cInk);
-		bg.hLine(L.mapX + mpad, L.footY, L.mapX + L.mapW - mpad, cInkSoft);
-
-		const Common::Rect sr(L.sideX, L.sideY, L.sideX + L.sideW, L.sideY + L.sideH);
-		bg.fillRect(sr, cStoneDark);
-		automapNoise(bg, sr.left, sr.top, L.sideW, L.sideH, cStoneHi, cStoneEdge, 0xc2b2ae35u);
-		automapBevel(bg, sr, cStoneEdge, cStoneHi); // sunken: dark top/left
-
-		// Plaque face only; its text is drawn per frame.
-		bg.fillRect(Common::Rect(L.plX, L.plY, L.plX + L.plW, L.plY + L.plH), cPlaqueEd);
-		bg.fillRect(Common::Rect(L.plX + 2, L.plY + 2, L.plX + L.plW - 2, L.plY + L.plH - 2), cPlaqueBg);
-
-		const int lx = L.plX, colW = L.plW;
-		const int rowH = fh * sc + MAX(4, sc * 4);
-		int cyy = L.plY + L.plH + MAX(8, sc * 6);
-
-		automapDrawBigString(bg, bigFont, "- LEGEND -", lx, cyy, colW, cGoldDim, sc);
-		cyy += fh * sc + MAX(4, sc * 3);
-		bg.hLine(lx, cyy, lx + colW - 1, cStoneHi);
-		bg.hLine(lx, cyy + 1, lx + colW - 1, cStoneEdge);
-		cyy += MAX(6, sc * 4);
-
-		static const char *const legendLbl[8] = {
-			"Party", "Stairs up", "Stairs down", "Teleporter", "Door", "Lever", "Niche", "Note"
-		};
-		const int isz = MAX(6, fh * sc);
-		for (int i = 0; i < 8; ++i) {
-			const int ix = lx, iy = cyy;
-			switch (i) {
-			case 0: automapFillTri(bg, ix + isz / 2, iy, ix + isz, iy + isz, ix, iy + isz, cParty); break;       // party (up)
-			case 1: automapFillTri(bg, ix, iy + isz, ix + isz, iy + isz, ix + isz / 2, iy, cStair); break;       // stairs up
-			case 2: automapFillTri(bg, ix, iy, ix + isz, iy, ix + isz / 2, iy + isz, cStair); break;             // stairs down
-			case 3: bg.frameRect(Common::Rect(ix, iy, ix + isz, iy + isz), cTele);
-			        bg.frameRect(Common::Rect(ix + 1, iy + 1, ix + isz - 1, iy + isz - 1), cTele); break;        // teleporter
-			case 4: bg.fillRect(Common::Rect(ix, iy + isz / 3, ix + isz, iy + isz - isz / 3), cDoor); break;     // door
-			case 5: bg.fillRect(Common::Rect(ix + isz / 3, iy, ix + isz - isz / 3, iy + isz), cLever); break;    // lever
-			case 6: bg.fillRect(Common::Rect(ix + isz / 3, iy, ix + isz - isz / 3, iy + isz), cNiche); break;    // niche
-			default: bg.fillRect(Common::Rect(ix, iy, ix + isz, iy + isz), cNote); break;                        // note
-			}
-			const int tx = lx + isz + MAX(4, sc * 2);
-			const int tw = colW - isz - MAX(4, sc * 2);
-			const int lsc = automapFit(bigFont, legendLbl[i], tw, sc);
-			automapDrawBigString(bg, bigFont, legendLbl[i], tx, iy + (isz - fh * lsc) / 2, tw, cPanelTxt, lsc, Graphics::kTextAlignLeft);
-			cyy += rowH;
-		}
-
-		cyy += MAX(6, sc * 5);
-		automapDrawBigString(bg, bigFont, "- KEYS -", lx, cyy, colW, cGoldDim, sc);
-		cyy += fh * sc + MAX(4, sc * 3);
-		bg.hLine(lx, cyy, lx + colW - 1, cStoneHi);
-		bg.hLine(lx, cyy + 1, lx + colW - 1, cStoneEdge);
-		cyy += MAX(6, sc * 4);
-		const int chipPad = MAX(2, sc * 2);
-		const int chipW = (bigFont ? bigFont->getStringWidth("N") * sc : 6 * sc) + chipPad * 2;
-		const int chipH = fh * sc + chipPad;
-		bg.fillRect(Common::Rect(lx, cyy, lx + chipW, cyy + chipH), cPlaqueEd);
-		bg.fillRect(Common::Rect(lx + 1, cyy + 1, lx + chipW - 1, cyy + chipH - 1), cPlaqueBg);
-		automapDrawBigString(bg, bigFont, "N", lx, cyy + chipPad / 2, chipW, cGold, sc);
-		automapDrawBigString(bg, bigFont, "Add note", lx + chipW + MAX(4, sc * 3), cyy + (chipH - fh * sc) / 2,
-			colW - chipW - MAX(4, sc * 3), cPanelTxt, sc, Graphics::kTextAlignLeft);
-	}
-
-	// Per frame: copy the chrome into the reused surface, then draw dynamics on top.
-	Graphics::Surface &surf = _automapFrame;
-	surf.copyRectToSurface(_automapBg, 0, 0, Common::Rect(0, 0, ow, oh));
-
-	const int cell = L.cell;
-	const int offX = L.offX;
-	const int offY = L.offY;
-	const int wt = MAX(2, cell / 5); // wall bar thickness
-
-	for (int by = 0; by < 32; ++by) {
-		for (int bx = 0; bx < 32; ++bx) {
-			const uint16 block = (by << 5) | bx;
-			const bool visited = automapIsVisited(block);
-			const bool seen = !visited && automapIsSeen(block);
-			if (!visited && !seen)
-				continue;
-
-			const int sx = offX + bx * cell;
-			const int sy = offY + by * cell;
-
-			// A side is a wall only where the party can't step (neighbour's facing wall, dir^2).
-			bool wall[4];
-			for (int d = 0; d < 4; ++d) {
-				const uint16 nb = calcNewBlockPosition(block, d);
-				wall[d] = !(_wllWallFlags[_levelBlockProperties[nb].walls[d ^ 2]] & 1);
-			}
-
-			// Cache the door bits: an open door loses its wall flag mid-animation but
-			// should still draw as a door.
-			const LevelBlockProperty *bp = &_levelBlockProperties[block];
-			const uint32 bkey = automapNoteKey(block);
-			uint8 dbits = _automapDoorBits.contains(bkey) ? _automapDoorBits[bkey] : 0;
-			if ((_wllWallFlags[bp->walls[0]] & 8) || (_wllWallFlags[bp->walls[2]] & 8))
-				dbits |= 1;
-			if ((_wllWallFlags[bp->walls[1]] & 8) || (_wllWallFlags[bp->walls[3]] & 8))
-				dbits |= 2;
-			if (dbits)
-				_automapDoorBits[bkey] = dbits;
-			const bool doorNS = (dbits & 1) != 0;
-			const bool doorEW = (dbits & 2) != 0;
-			if (doorNS)
-				wall[0] = wall[2] = false;
-			if (doorEW)
-				wall[1] = wall[3] = false;
-
-			surf.fillRect(Common::Rect(sx, sy, sx + cell, sy + cell), visited ? cFloor : cFloorSeen);
-			if (visited) {
-				surf.hLine(sx, sy, sx + cell - 1, cGrid);
-				surf.vLine(sx, sy, sy + cell - 1, cGrid);
-			}
-			const uint32 wc = visited ? cWall : cWallSeen;
-			if (wall[0]) surf.fillRect(Common::Rect(sx, sy, sx + cell, sy + wt), wc);
-			if (wall[1]) surf.fillRect(Common::Rect(sx + cell - wt, sy, sx + cell, sy + cell), wc);
-			if (wall[2]) surf.fillRect(Common::Rect(sx, sy + cell - wt, sx + cell, sy + cell), wc);
-			if (wall[3]) surf.fillRect(Common::Rect(sx, sy, sx + wt, sy + cell), wc);
-
-			if (doorNS || doorEW) {
-				const int dt = MAX(2, wt + 1);
-				const int dm = MAX(1, cell / 5);
-				const int dcx = sx + cell / 2, dcy = sy + cell / 2;
-				if (doorNS)
-					surf.fillRect(Common::Rect(sx + dm, dcy - dt / 2, sx + cell - dm, dcy - dt / 2 + dt), cDoor);
-				if (doorEW)
-					surf.fillRect(Common::Rect(dcx - dt / 2, sy + dm, dcx - dt / 2 + dt, sy + cell - dm), cDoor);
-			}
-
-			// Special-wall pip on the carrying side (skip door types 5/6; 10 = niche).
-			for (int d = 0; d < 4; ++d) {
-				const uint8 st = _specialWallTypes[bp->walls[d]];
-				if (st == 0 || st == 5 || st == 6)
-					continue;
-				const uint32 pcol = (st == 10) ? cNiche : cLever;
-				const int ps = MAX(2, cell / 4);
-				int px, py;
-				switch (d) {
-				case 0:  px = sx + cell / 2 - ps / 2; py = sy; break;
-				case 1:  px = sx + cell - ps;         py = sy + cell / 2 - ps / 2; break;
-				case 2:  px = sx + cell / 2 - ps / 2; py = sy + cell - ps; break;
-				default: px = sx;                     py = sy + cell / 2 - ps / 2; break;
-				}
-				surf.fillRect(Common::Rect(px, py, px + ps, py + ps), pcol);
-			}
-
-			if (_automapIcons.contains(bkey)) {
-				const uint8 ic = _automapIcons[bkey];
-				uint32 col = (ic == kAmTeleport) ? cTele : cStair;
-				if (!visited)
-					col = cWallSeen;
-				automapDrawIcon(surf, sx, sy, cell, ic, col);
-			}
-
-			if (_automapNotes.contains(bkey)) {
-				const int ds = MAX(2, cell / 3);
-				surf.fillRect(Common::Rect(sx + cell - ds, sy, sx + cell, sy + ds), cNote);
-			}
-		}
-	}
-
-	if (_automapSelectedBlock != 0xFFFF) {
-		const int sx = offX + (_automapSelectedBlock & 0x1F) * cell;
-		const int sy = offY + (_automapSelectedBlock >> 5) * cell;
-		surf.frameRect(Common::Rect(sx, sy, sx + cell, sy + cell), cSel);
-		surf.frameRect(Common::Rect(sx + 1, sy + 1, sx + cell - 1, sy + cell - 1), cSel);
-	}
-
-	const int mx = offX + (_currentBlock & 0x1F) * cell;
-	const int my = offY + (_currentBlock >> 5) * cell;
-	const int cx = mx + cell / 2;
-	const int cyp = my + cell / 2;
-	const int r = MAX(2, cell / 2 - MAX(1, cell / 6));
-	const int b = MAX(2, r * 3 / 4);
-	int tipX, tipY, l1X, l1Y, l2X, l2Y;
-	switch (_currentDirection) {
-	case 1:  tipX = cx + r; tipY = cyp; l1X = cx - b; l1Y = cyp - b; l2X = cx - b; l2Y = cyp + b; break;
-	case 2:  tipX = cx; tipY = cyp + r; l1X = cx - b; l1Y = cyp - b; l2X = cx + b; l2Y = cyp - b; break;
-	case 3:  tipX = cx - r; tipY = cyp; l1X = cx + b; l1Y = cyp - b; l2X = cx + b; l2Y = cyp + b; break;
-	default: tipX = cx; tipY = cyp - r; l1X = cx - b; l1Y = cyp + b; l2X = cx + b; l2Y = cyp + b; break;
-	}
-	automapFillTri(surf, tipX, tipY, l1X, l1Y, l2X, l2Y, cPartyEdge);
-	const int gx = (tipX + l1X + l2X) / 3, gy = (tipY + l1Y + l2Y) / 3;
-	automapFillTri(surf,
-		tipX + (gx - tipX) / 4, tipY + (gy - tipY) / 4,
-		l1X + (gx - l1X) / 4, l1Y + (gy - l1Y) / 4,
-		l2X + (gx - l2X) / 4, l2Y + (gy - l2Y) / 4, cParty);
-
-	// Plaque text: area name over coords, in two size-fitted bands so they never overlap.
-	if (bigFont) {
-		const uint16 cb = (_automapSelectedBlock != 0xFFFF) ? _automapSelectedBlock : _currentBlock;
-		const Common::String lvl = automapLevelName();
-		const Common::String crd = Common::String::format("X %d   Y %d", cb & 0x1F, cb >> 5);
-		const int pm = MAX(3, sc * 2);
-		const int innerW = L.plW - 2 * pm;
-		const int innerH = L.plH - 2 * pm;
-		const int gap = MAX(2, innerH / 12);
-		const int lvlBand = (innerH - gap) * 6 / 10; // level gets ~60%, coords ~40%
-		const int crdBand = (innerH - gap) - lvlBand;
-		const int lvlSc = automapFit(bigFont, lvl, innerW, MAX(1, lvlBand / fh));
-		const int crdSc = automapFit(bigFont, crd, innerW, MAX(1, crdBand / fh));
-		const int lvlY = L.plY + pm + (lvlBand - fh * lvlSc) / 2;
-		const int crdY = L.plY + pm + lvlBand + gap + (crdBand - fh * crdSc) / 2;
-		automapDrawBigString(surf, bigFont, lvl, L.plX, lvlY, L.plW, cGold, lvlSc);
-		automapDrawBigString(surf, bigFont, crd, L.plX, crdY, L.plW, cPanelTxt, crdSc);
-	}
-
-	// Footer: the selected cell's note / auto-info (the KEYS panel documents N).
-	if (bigFont) {
-		Common::String foot;
-		if (_automapEditing) {
-			const char *caret = ((_system->getMillis() / 400) & 1) ? "_" : " ";
-			foot = Common::String("Note: ") + _automapEditBuffer + caret;
-		} else if (_automapSelectedBlock != 0xFFFF) {
-			const uint32 selKey = automapNoteKey(_automapSelectedBlock);
-			Common::String body;
-			if (_automapNotes.contains(selKey)) {
-				body = _automapNotes[selKey];
-			} else {
-				if (_automapAutoInfo.contains(selKey))
-					body = _automapAutoInfo[selKey];
-				const Common::String items = automapLiveItems(_automapSelectedBlock);
-				if (!items.empty())
-					body = body.empty() ? items : body + " - " + items;
-			}
-			if (!body.empty())
-				foot = Common::String("Selected: ") + body;
-		}
-		if (!foot.empty()) {
-			const int footH = (L.mapY + L.mapH) - L.footY;
-			const int fpad = MAX(2, footH / 6);
-			const int maxFsc = MAX(1, (footH - 2 * fpad) / fh);
-			const int fw = L.mapW - 2 * mpad;
-			const int fsc = MIN(automapFit(bigFont, foot, fw, sc), maxFsc);
-			const int fy = L.footY + (footH - fh * fsc) / 2;
-			automapDrawBigString(surf, bigFont, foot, L.mapX + mpad, fy, fw, cInk, fsc, Graphics::kTextAlignLeft);
-		}
-	}
-
-	_system->copyRectToOverlay(surf.getPixels(), surf.pitch, 0, 0, ow, oh);
-	_system->updateScreen();
-}
-
 } // End of namespace Kyra
 
 #endif // ENABLE_EOB
diff --git a/engines/kyra/gui/saveload_eob.cpp b/engines/kyra/gui/saveload_eob.cpp
index ae79d6eab29..d0e3b864754 100644
--- a/engines/kyra/gui/saveload_eob.cpp
+++ b/engines/kyra/gui/saveload_eob.cpp
@@ -21,6 +21,8 @@
 
 #ifdef ENABLE_EOB
 
+#include "kyra/kyra_v1.h"
+#include "kyra/gui/automap_eob.h"
 #include "kyra/resource/resource.h"
 #include "kyra/script/script_eob.h"
 
@@ -34,24 +36,6 @@
 
 namespace Kyra {
 
-// Automap key->string maps: a count, then key + null-terminated string per entry.
-static void automapReadStringMap(Common::ReadStream &in, Common::HashMap<uint32, Common::String> &map) {
-	uint32 n = in.readUint32BE();
-	for (uint32 i = 0; i < n; ++i) {
-		uint32 k = in.readUint32BE();
-		map[k] = in.readString();
-	}
-}
-
-static void automapWriteStringMap(Common::WriteStream *out, const Common::HashMap<uint32, Common::String> &map) {
-	out->writeUint32BE(map.size());
-	for (Common::HashMap<uint32, Common::String>::const_iterator it = map.begin(); it != map.end(); ++it) {
-		out->writeUint32BE(it->_key);
-		out->writeString(it->_value);
-		out->writeByte(0);
-	}
-}
-
 Common::Error EoBCoreEngine::loadGameState(int slot) {
 	// Special slot id -1 for EOB1 party transfer
 	const char *fileName = (slot == -1) ? _savegameFilename.c_str() : getSavegameFilename(slot);
@@ -141,6 +125,7 @@ Common::Error EoBCoreEngine::loadGameState(int slot) {
 		_currentDirection = in.readUint16BE();
 		_itemInHand = in.readSint16BE();
 		_hasTempDataFlags = in.readUint32BE();
+		_hasTempDataMapFlags = (header.version >= 25) ? in.readUint32BE() : 0;
 		_partyEffectFlags = in.readUint32BE();
 
 		_updateFlags = in.readUint16BE();
@@ -222,13 +207,16 @@ Common::Error EoBCoreEngine::loadGameState(int slot) {
 			releaseMonsterTempData(_lvlTempData[i]);
 			releaseFlyingObjectTempData(_lvlTempData[i]);
 			releaseWallOfForceTempData(_lvlTempData[i]);
+			releaseAutoMapTempData(_lvlTempData[i]);
 			delete _lvlTempData[i];
 		}
 
 		_lvlTempData[i] = new LevelTempData;
 		LevelTempData *l = _lvlTempData[i];
-		l->wallsXorData = new uint8[4096];
-		l->flags = new uint16[1024];
+		uint8 *d = new uint8[4096]();
+		l->wallsXorData = d;
+		uint16 *bflags = new uint16[1024]();
+		l->flags = bflags;
 		EoBMonsterInPlay *lm = new EoBMonsterInPlay[30];
 		l->monsters = lm;
 		EoBFlyingObject *lf = new EoBFlyingObject[_numFlyingObjects];
@@ -236,9 +224,17 @@ Common::Error EoBCoreEngine::loadGameState(int slot) {
 		WallOfForce *lw = new WallOfForce[5];
 		l->wallsOfForce = lw;
 
-		in.read(l->wallsXorData, 4096);
+		in.read(d, 4096);
 		for (int ii = 0; ii < 1024; ii++)
-			l->flags[ii] = in.readByte();
+			bflags[ii] = in.readByte();
+
+		l->automapExploreState = nullptr;
+		if (_hasTempDataMapFlags & (1 << i)) {
+			uint32 *e = new uint32[64]();
+			l->automapExploreState = e;
+			for (int ii = 0; ii < 64; ii++)
+				*e++ = in.readUint32BE();
+		}
 
 		for (int ii = 0; ii < 30; ii++) {
 			EoBMonsterInPlay *m = &lm[ii];
@@ -298,25 +294,8 @@ Common::Error EoBCoreEngine::loadGameState(int slot) {
 		}
 	}
 
-	// Automap data (save version >= 25). Clear first (incl. the transient door-bit
-	// cache, which must not carry over) and only read when the save is new enough.
-	memset(_automapVisited, 0, sizeof(_automapVisited));
-	memset(_automapSeen, 0, sizeof(_automapSeen));
-	_automapNotes.clear();
-	_automapIcons.clear();
-	_automapAutoInfo.clear();
-	_automapDoorBits.clear();
-	if (header.version >= 25) {
-		in.read(_automapVisited, sizeof(_automapVisited));
-		in.read(_automapSeen, sizeof(_automapSeen));
-		automapReadStringMap(in, _automapNotes);
-		uint32 numIcons = in.readUint32BE();
-		for (uint32 i = 0; i < numIcons; ++i) {
-			uint32 k = in.readUint32BE();
-			_automapIcons[k] = in.readByte();
-		}
-		automapReadStringMap(in, _automapAutoInfo);
-	}
+	if (header.version >= 25)
+		_automap->loadStrings(&in);
 
 	loadLevel(_currentLevel, _currentSub);
 	if (_flags.platform == Common::kPlatformFMTowns && _gameToLoad != -1)
@@ -453,6 +432,7 @@ Common::Error EoBCoreEngine::saveGameStateIntern(int slot, const char *saveName,
 	out->writeUint16BE(_currentDirection);
 	out->writeSint16BE(_itemInHand);
 	out->writeUint32BE(_hasTempDataFlags);
+	out->writeUint32BE(_hasTempDataMapFlags);
 	out->writeUint32BE(_partyEffectFlags);
 
 	out->writeUint16BE(_updateFlags);
@@ -520,12 +500,19 @@ Common::Error EoBCoreEngine::saveGameStateIntern(int slot, const char *saveName,
 		for (int ii = 0; ii < 1024; ii++)
 			out->writeByte(l->flags[ii] & 0xFF);
 
-		EoBMonsterInPlay *lm = (EoBMonsterInPlay *)_lvlTempData[i]->monsters;
-		EoBFlyingObject *lf = (EoBFlyingObject *)_lvlTempData[i]->flyingObjects;
-		WallOfForce *lw = (WallOfForce *)_lvlTempData[i]->wallsOfForce;
+		if (_hasTempDataMapFlags & (1 << i)) {
+			const uint32 *e = reinterpret_cast<const uint32*>(l->automapExploreState);
+			assert(e);
+			for (int ii = 0; ii < 64; ii++)
+				out->writeUint32BE(*e++);
+		}
+
+		const EoBMonsterInPlay *lm = reinterpret_cast<const EoBMonsterInPlay*>(_lvlTempData[i]->monsters);
+		const EoBFlyingObject *lf = reinterpret_cast<const EoBFlyingObject*>(_lvlTempData[i]->flyingObjects);
+		const WallOfForce *lw = reinterpret_cast<const WallOfForce*>(_lvlTempData[i]->wallsOfForce);
 
 		for (int ii = 0; ii < 30; ii++) {
-			EoBMonsterInPlay *m = &lm[ii];
+			const EoBMonsterInPlay *m = &lm[ii];
 			out->writeByte(m->type);
 			out->writeByte(m->unit);
 			out->writeUint16BE(m->block);
@@ -553,7 +540,7 @@ Common::Error EoBCoreEngine::saveGameStateIntern(int slot, const char *saveName,
 		}
 
 		for (int ii = 0; ii < _numFlyingObjects; ii++) {
-			EoBFlyingObject *m = &lf[ii];
+			const EoBFlyingObject *m = &lf[ii];
 			out->writeByte(m->enable);
 			out->writeByte(m->objectType);
 			out->writeSint16BE(m->attackerId);
@@ -570,7 +557,7 @@ Common::Error EoBCoreEngine::saveGameStateIntern(int slot, const char *saveName,
 		}
 
 		for (int ii = 0; ii < 5; ii++) {
-			WallOfForce *w = &lw[ii];
+			const WallOfForce *w = &lw[ii];
 			out->writeUint16BE(w->block);
 			out->writeUint32BE(w->duration);
 		}
@@ -911,28 +898,28 @@ Common::String EoBCoreEngine::readOriginalSaveFile(const Common::Path &file) {
 
 		_lvlTempData[i] = new LevelTempData;
 		LevelTempData *l = _lvlTempData[i];
-		l->wallsXorData = new uint8[4096];
-		l->flags = new uint16[1024];
-		memset(l->flags, 0, 1024 * sizeof(uint16));
+		uint16 *bflags = new uint16[1024]();
+		l->flags = bflags;
 		EoBMonsterInPlay *lm = new EoBMonsterInPlay[30]();
 		l->monsters = lm;
 		EoBFlyingObject *lf = new EoBFlyingObject[_numFlyingObjects]();
 		l->flyingObjects = lf;
 		WallOfForce *lw = new WallOfForce[5]();
 		l->wallsOfForce = lw;
+		uint8 *d = new uint8[4096];
+		l->wallsXorData = d;
 
 		if (sourcePlatform == Common::kPlatformFMTowns) {
-			in.read(l->wallsXorData, 4096);
+			in.read(d, 4096);
 		} else {
 			in.read(cmpData, 1200);
-			_screen->decodeFrame4(cmpData, l->wallsXorData, 4096);
+			_screen->decodeFrame4(cmpData, d, 4096);
 		}
 		_curBlockFile = getBlockFileName(i + 1, 0);
 		const uint8 *p = getBlockFileData(i + 1);
 		uint16 len = READ_LE_UINT16(p + 4);
 		p += 6;
 
-		uint8 *d = l->wallsXorData;
 		for (int ii = 0; ii < 1024; ii++) {
 			for (int iii = 0; iii < 4; iii++)
 				*d++ ^= p[ii * len + iii];
@@ -978,7 +965,7 @@ Common::String EoBCoreEngine::readOriginalSaveFile(const Common::Path &file) {
 				m->sub = in.readByte();
 			}
 
-			l->flags[m->block]++;
+			bflags[m->block]++;
 		}
 
 		if (_flags.gameID == GI_EOB1)
@@ -1372,7 +1359,7 @@ bool EoBCoreEngine::saveAsOriginalSaveFile(int slot) {
 			out->writeUint32BE(0);
 
 		for (int ii = 0; ii < 30; ii++) {
-			EoBMonsterInPlay *m = &((EoBMonsterInPlay*)l->monsters)[ii];
+			const EoBMonsterInPlay *m = &(reinterpret_cast<const EoBMonsterInPlay*>(l->monsters)[ii]);
 			out->writeByte(m->type);
 			out->writeByte(m->unit);
 			if (_flags.platform == Common::kPlatformAmiga)
@@ -1426,7 +1413,7 @@ bool EoBCoreEngine::saveAsOriginalSaveFile(int slot) {
 			continue;
 
 		for (int ii = 0; ii < 5; ii++) {
-			WallOfForce *w= &((WallOfForce*)l->wallsOfForce)[ii];
+			const WallOfForce *w= &(reinterpret_cast<const WallOfForce*>(l->wallsOfForce)[ii]);
 			if (_flags.platform == Common::kPlatformAmiga) {
 				out->writeUint16BE(w->block);
 				out->writeUint32BE(w->duration / _tickLength);
@@ -1477,7 +1464,7 @@ bool EoBCoreEngine::saveAsOriginalSaveFile(int slot) {
 	return true;
 }
 
-void *EoBCoreEngine::generateMonsterTempData(LevelTempData *tmp) {
+const void *EoBCoreEngine::generateMonsterTempData(uint8 &monsterDifficulty) const {
 	EoBMonsterInPlay *m = new EoBMonsterInPlay[30];
 	memcpy(m, _monsters,  sizeof(EoBMonsterInPlay) * 30);
 	return m;
@@ -1488,11 +1475,10 @@ void EoBCoreEngine::restoreMonsterTempData(LevelTempData *tmp) {
 }
 
 void EoBCoreEngine::releaseMonsterTempData(LevelTempData *tmp) {
-	EoBMonsterInPlay *p = (EoBMonsterInPlay *)tmp->monsters;
-	delete[] p;
+	delete[] reinterpret_cast<const EoBMonsterInPlay*>(tmp->monsters);
 }
 
-void *EoBCoreEngine::generateWallOfForceTempData(LevelTempData *tmp) {
+const void *EoBCoreEngine::generateWallOfForceTempData() const {
 	WallOfForce *w = new WallOfForce[5];
 	memcpy(w, _wallsOfForce,  sizeof(WallOfForce) * 5);
 	uint32 ct = _system->getMillis();
@@ -1509,8 +1495,28 @@ void EoBCoreEngine::restoreWallOfForceTempData(LevelTempData *tmp) {
 }
 
 void EoBCoreEngine::releaseWallOfForceTempData(LevelTempData *tmp) {
-	WallOfForce *p = (WallOfForce *)tmp->wallsOfForce;
-	delete[] p;
+	delete[] reinterpret_cast<const WallOfForce*>(tmp->wallsOfForce);
+}
+
+const void *EoBCoreEngine::generateAutoMapTempData() {
+	uint32 *e = new uint32[64]();
+	for (int i = 0; i < 1024; i++)
+		e[i >> 4] |= (_levelBlockProperties[i].direction & 3) << ((i << 1) & 0x1E);
+	_hasTempDataMapFlags |= (1 << (_currentLevel - 1));
+	return e;
+}
+
+void EoBCoreEngine::restoreAutoMapTempData(LevelTempData *tmp) {
+	const uint32 *e = reinterpret_cast<const uint32*>(tmp->automapExploreState);
+	if (e == nullptr)
+		return;
+
+	for (int i = 0; i < 1024; i++)
+		_levelBlockProperties[i].direction = (e[i >> 4] >> ((i << 1) & 0x1E)) & 3;
+}
+
+void EoBCoreEngine::releaseAutoMapTempData(LevelTempData *tmp) {
+	delete[] reinterpret_cast<const uint32*>(tmp->automapExploreState);
 }
 
 } // End of namespace Kyra
diff --git a/engines/kyra/gui/saveload_lol.cpp b/engines/kyra/gui/saveload_lol.cpp
index 20393f9ca5a..1b6ce33c7cd 100644
--- a/engines/kyra/gui/saveload_lol.cpp
+++ b/engines/kyra/gui/saveload_lol.cpp
@@ -222,12 +222,15 @@ Common::Error LoLEngine::loadGameState(int slot) {
 			releaseMonsterTempData(_lvlTempData[i]);
 			releaseFlyingObjectTempData(_lvlTempData[i]);
 			releaseWallOfForceTempData(_lvlTempData[i]);
+			releaseAutoMapTempData(_lvlTempData[i]);
 			delete _lvlTempData[i];
 		}
 
 		_lvlTempData[i] = new LevelTempData;
-		_lvlTempData[i]->wallsXorData = new uint8[4096];
-		_lvlTempData[i]->flags = new uint16[1024];
+		uint8 *wd = new uint8[4096]();
+		_lvlTempData[i]->wallsXorData = wd;
+		uint16 *wf = new uint16[1024]();
+		_lvlTempData[i]->flags = wf;
 		LoLMonster *lm = new LoLMonster[30];
 		_lvlTempData[i]->monsters = lm;
 		FlyingObject *lf = new FlyingObject[_numFlyingObjects];
@@ -240,13 +243,13 @@ Common::Error LoLEngine::loadGameState(int slot) {
 			in.skip(4);
 			in.read(origCmp, in.readUint16());
 			_screen->decodeFrame4(origCmp, _tempBuffer5120, 5120);
-			memcpy(l->wallsXorData, _tempBuffer5120, 4096);
+			memcpy(wd, _tempBuffer5120, 4096);
 			for (int ii = 0; ii < 1024; ii++)
-				l->flags[ii] = _tempBuffer5120[4096 + ii];
+				*wf++ = _tempBuffer5120[4096 + ii];
 		} else {
-			in.read(l->wallsXorData, 4096);
+			in.read(wd, 4096);
 			for (int ii = 0; ii < 1024; ii++)
-				l->flags[ii] = in.readByte();
+				*wf++ = in.readByte();
 		}
 
 		if (header.originalSave)
@@ -440,11 +443,11 @@ Common::Error LoLEngine::saveGameStateIntern(int slot, const char *saveName, con
 		for (int ii = 0; ii < 1024; ii++)
 			out->writeByte(l->flags[ii] & 0xFF);
 
-		LoLMonster *lm = (LoLMonster *)_lvlTempData[i]->monsters;
-		FlyingObject *lf = (FlyingObject *)_lvlTempData[i]->flyingObjects;
+		const LoLMonster *lm = reinterpret_cast<const LoLMonster*>(_lvlTempData[i]->monsters);
+		const FlyingObject *lf = reinterpret_cast<const FlyingObject*>(_lvlTempData[i]->flyingObjects);
 
 		for (int ii = 0; ii < 30; ii++) {
-			LoLMonster *m = &lm[ii];
+			const LoLMonster *m = &lm[ii];
 			out->writeUint16BE(m->nextAssignedObject);
 			out->writeUint16BE(m->nextDrawObject);
 			out->writeByte(m->flyingHeight);
@@ -476,7 +479,7 @@ Common::Error LoLEngine::saveGameStateIntern(int slot, const char *saveName, con
 		}
 
 		for (int ii = 0; ii < _numFlyingObjects; ii++) {
-			FlyingObject *m = &lf[ii];
+			const FlyingObject *m = &lf[ii];
 			out->writeByte(m->enable);
 			out->writeByte(m->objectType);
 			out->writeUint16BE(m->attackerId);
@@ -537,10 +540,10 @@ void LoLEngine::restoreBlockTempData(int levelIndex) {
 	restoreTempDataAdjustMonsterStrength(levelIndex - 1);
 }
 
-void *LoLEngine::generateMonsterTempData(LevelTempData *tmp) {
+const void *LoLEngine::generateMonsterTempData(uint8 &monsterDifficulty) const {
 	LoLMonster *m = new LoLMonster[30];
 	memcpy(m, _monsters,  sizeof(LoLMonster) * 30);
-	tmp->monsterDifficulty = _monsterDifficulty;
+	monsterDifficulty = _monsterDifficulty;
 	return m;
 }
 
@@ -575,8 +578,7 @@ void LoLEngine::restoreMonsterTempData(LevelTempData *tmp) {
 }
 
 void LoLEngine::releaseMonsterTempData(LevelTempData *tmp) {
-	LoLMonster *p = (LoLMonster *)tmp->monsters;
-	delete[] p;
+	delete[] reinterpret_cast<const LoLMonster*>(tmp->monsters);
 }
 
 } // End of namespace Kyra
diff --git a/engines/kyra/gui/saveload_rpg.cpp b/engines/kyra/gui/saveload_rpg.cpp
index d5c2182b2f9..a3565142578 100644
--- a/engines/kyra/gui/saveload_rpg.cpp
+++ b/engines/kyra/gui/saveload_rpg.cpp
@@ -38,22 +38,20 @@ void KyraRpgEngine::generateTempData() {
 		releaseMonsterTempData(_lvlTempData[l]);
 		releaseFlyingObjectTempData(_lvlTempData[l]);
 		releaseWallOfForceTempData(_lvlTempData[l]);
+		releaseAutoMapTempData(_lvlTempData[l]);
 		delete _lvlTempData[l];
 	}
 
 	_lvlTempData[l] = new LevelTempData;
 
-	_lvlTempData[l]->wallsXorData = new uint8[4096];
-	_lvlTempData[l]->flags = new uint16[1024];
-
 	const uint8 *p = getBlockFileData(_currentLevel);
 	uint16 len = READ_LE_UINT16(p + 4);
 	p += 6;
 
-	memset(_lvlTempData[l]->wallsXorData, 0, 4096);
-	memset(_lvlTempData[l]->flags, 0, 1024 * sizeof(uint16));
-	uint8 *d = _lvlTempData[l]->wallsXorData;
-	uint16 *df = _lvlTempData[l]->flags;
+	uint8 *d = new uint8[4096]();
+	_lvlTempData[l]->wallsXorData = d;
+	uint16 *df = new uint16[1024]();
+	_lvlTempData[l]->flags = df;
 
 	for (int i = 0; i < 1024; i++) {
 		for (int ii = 0; ii < 4; ii++)
@@ -61,9 +59,10 @@ void KyraRpgEngine::generateTempData() {
 		*df++ = _levelBlockProperties[i].flags;
 	}
 
-	_lvlTempData[l]->monsters = generateMonsterTempData(_lvlTempData[l]);
-	_lvlTempData[l]->flyingObjects = generateFlyingObjectTempData(_lvlTempData[l]);
-	_lvlTempData[l]->wallsOfForce = generateWallOfForceTempData(_lvlTempData[l]);
+	_lvlTempData[l]->monsters = generateMonsterTempData(_lvlTempData[l]->monsterDifficulty);
+	_lvlTempData[l]->flyingObjects = generateFlyingObjectTempData();
+	_lvlTempData[l]->wallsOfForce = generateWallOfForceTempData();
+	_lvlTempData[l]->automapExploreState = generateAutoMapTempData();
 
 	_hasTempDataFlags |= (1 << l);
 }
@@ -76,8 +75,8 @@ void KyraRpgEngine::restoreBlockTempData(int levelIndex) {
 
 	memset(_levelBlockProperties, 0, 1024 * sizeof(LevelBlockProperty));
 
-	uint8 *t = _lvlTempData[l]->wallsXorData;
-	uint16 *t2 = _lvlTempData[l]->flags;
+	const uint8 *t = _lvlTempData[l]->wallsXorData;
+	const uint16 *t2 = _lvlTempData[l]->flags;
 
 	for (int i = 0; i < 1024; i++) {
 		for (int ii = 0; ii < 4; ii++)
@@ -88,6 +87,7 @@ void KyraRpgEngine::restoreBlockTempData(int levelIndex) {
 	restoreMonsterTempData(_lvlTempData[l]);
 	restoreFlyingObjectTempData(_lvlTempData[l]);
 	restoreWallOfForceTempData(_lvlTempData[l]);
+	restoreAutoMapTempData(_lvlTempData[l]);
 }
 
 void KyraRpgEngine::releaseTempData() {
@@ -98,13 +98,14 @@ void KyraRpgEngine::releaseTempData() {
 			releaseMonsterTempData(_lvlTempData[i]);
 			releaseFlyingObjectTempData(_lvlTempData[i]);
 			releaseWallOfForceTempData(_lvlTempData[i]);
+			releaseAutoMapTempData(_lvlTempData[i]);
 			delete _lvlTempData[i];
 			_lvlTempData[i] = 0;
 		}
 	}
 }
 
-void *KyraRpgEngine::generateFlyingObjectTempData(LevelTempData *tmp) {
+const void *KyraRpgEngine::generateFlyingObjectTempData() const {
 	assert(_flyingObjectStructSize == sizeof(EoBFlyingObject));
 	EoBFlyingObject *f = new EoBFlyingObject[_numFlyingObjects];
 	memcpy(f, _flyingObjectsPtr,  sizeof(EoBFlyingObject) * _numFlyingObjects);
@@ -117,8 +118,7 @@ void KyraRpgEngine::restoreFlyingObjectTempData(LevelTempData *tmp) {
 }
 
 void KyraRpgEngine::releaseFlyingObjectTempData(LevelTempData *tmp) {
-	EoBFlyingObject *p = (EoBFlyingObject *)tmp->flyingObjects;
-	delete[] p;
+	delete[] reinterpret_cast<const EoBFlyingObject*>(tmp->flyingObjects);
 }
 
 } // End of namespace Kyra
diff --git a/engines/kyra/metaengine.cpp b/engines/kyra/metaengine.cpp
index a1e91600da5..2c25eedc970 100644
--- a/engines/kyra/metaengine.cpp
+++ b/engines/kyra/metaengine.cpp
@@ -196,7 +196,7 @@ const ADExtraGuiOptionsMap gameGuiOptions[] = {
 	{
 		GAMEOPTION_EOB_AUTOMAP,
 		{
-			_s("Automap"),
+			_s("Automap (ScummVM feature)"),
 			_s("Enable the in-game automap overlay (toggle in-game with Tab)"),
 			"automap",
 			true,
@@ -205,6 +205,18 @@ const ADExtraGuiOptionsMap gameGuiOptions[] = {
 		}
 	},
 
+	{
+		GAMEOPTION_EOB_AUTOMAP_SEGA,	// same as GAMEOPTION_EOB_AUTOMAP, but with disabled default, since the Sega CD version already has a regular automap
+		{
+			_s("Automap (ScummVM feature)"),
+			_s("Enable the in-game automap overlay (toggle in-game with Tab)"),
+			"automap",
+			false,
+			0,
+			0
+		}
+	},
+
 	AD_EXTRA_GUI_OPTIONS_TERMINATOR
 };
 
diff --git a/engines/kyra/module.mk b/engines/kyra/module.mk
index d4835218fa9..f51cfdf864a 100644
--- a/engines/kyra/module.mk
+++ b/engines/kyra/module.mk
@@ -127,6 +127,7 @@ MODULE_OBJS += \
 	graphics/screen_eob_pc98.o \
 	graphics/screen_eob_segacd.o \
 	graphics/screen_eob_towns.o \
+	gui/automap_eob.o \
 	gui/gui_eob.o \
 	gui/gui_eob_segacd.o \
 	gui/saveload_eob.o \


Commit: 5612c62a77bd5c3b60dbcf4b714d13b6fa95a0d7
    https://github.com/scummvm/scummvm/commit/5612c62a77bd5c3b60dbcf4b714d13b6fa95a0d7
Author: athrxx (athrxx at scummvm.org)
Date: 2026-08-25T22:51:35+02:00

Commit Message:
KYRA: (EOB automap feature) - remove annotations

This takes up quite some resources and exceeds what we actually
want in an autmap for a not very complex game. I don't think the
manual annotation would see much use. And listing the items
on the floor is too much, this seems more like a walkthrough.

Changed paths:
    engines/kyra/engine/scene_eob.cpp
    engines/kyra/gui/automap_eob.cpp
    engines/kyra/gui/automap_eob.h
    engines/kyra/gui/saveload_eob.cpp


diff --git a/engines/kyra/engine/scene_eob.cpp b/engines/kyra/engine/scene_eob.cpp
index 51bbf4ac011..4fed2f5335f 100644
--- a/engines/kyra/engine/scene_eob.cpp
+++ b/engines/kyra/engine/scene_eob.cpp
@@ -755,11 +755,6 @@ void EoBCoreEngine::moveParty(uint16 block) {
 
 	_automap->markVisited(block);
 
-	// Note the level before running scripts: if one moves the party elsewhere, the
-	// automap tags this cell as an exit.
-	const int preLevel = _currentLevel;
-	const uint16 trigBlock = block;
-
 	runLevelScript(old, 2);
 
 	if (++_moveCounter > 3) {
@@ -769,11 +764,6 @@ void EoBCoreEngine::moveParty(uint16 block) {
 
 	runLevelScript(block, 1);
 
-	if (_currentLevel != preLevel)
-		_automap->tagTransition(preLevel, trigBlock, _currentLevel);
-	else
-		_automap->collectCellInfo(preLevel, trigBlock, _teleporterWallId);
-
 	if (_flags.gameID == GI_EOB2 && _levelBlockProperties[block].walls[0] == 26)
 		memset(_levelBlockProperties[block].walls, 0, 4);
 
diff --git a/engines/kyra/gui/automap_eob.cpp b/engines/kyra/gui/automap_eob.cpp
index 21da897abfd..f84c54d301e 100644
--- a/engines/kyra/gui/automap_eob.cpp
+++ b/engines/kyra/gui/automap_eob.cpp
@@ -235,7 +235,7 @@ const Automap_EoB::TranslateableStrings Automap_EoB::_stringTable[] = {
 };
 
 Automap_EoB::Automap_EoB(OSystem *system, LevelBlockProperty **blockData, const uint8 *wllFlags, int gameID, int lang, bool featureEnabled) :
-	_system(system), _blockData(*blockData), _wllWallFlags(wllFlags), _enabled(featureEnabled), _visible(false), _selectedBlock(0xFFFF), _editing(false), _automapBg(nullptr),
+	_system(system), _blockData(*blockData), _wllWallFlags(wllFlags), _enabled(featureEnabled), _visible(false), _selectedBlock(0xFFFF), _automapBg(nullptr),
 	_automapFrame(nullptr), _levelNames(nullptr), _legendStrings(nullptr), _numLevelNames(gameID == GI_EOB1 ? 12 : (gameID == GI_EOB2 ? 16 : 0)) {
 	_automapBg = new Graphics::Surface();
 	_automapFrame = new Graphics::Surface();
@@ -244,6 +244,7 @@ Automap_EoB::Automap_EoB(OSystem *system, LevelBlockProperty **blockData, const
 	uint gameIndex = gameID - GI_EOB1;
 
 	switch (lang) {
+	// TODO: Add remaining languages here.
 	case Common::EN_ANY:
 	default:
 		break;
@@ -308,6 +309,7 @@ bool Automap_EoB::isSeen(uint16 block) const {
 	return (_blockData[block].direction & 1) != 0;
 }
 
+/*
 void Automap_EoB::tagTransition(int fromLevel, uint16 fromBlock, int toLevel) {
 	// On a script-driven level change, tag the cell we left with a stairs/teleport
 	// glyph and a "to level N" note (deeper level = down).
@@ -326,8 +328,8 @@ void Automap_EoB::tagTransition(int fromLevel, uint16 fromBlock, int toLevel) {
 		icon = kAmTeleport;
 		info = Common::String::format("Teleport to level %d", toLevel);
 	}
-	_automapIcons[k] = icon;
-	_automapAutoInfo[k] = info;
+	//_automapIcons[k] = icon;
+	//_automapAutoInfo[k] = info;
 }
 
 void Automap_EoB::collectCellInfo(int level, uint16 block, int teleporterWallId) {
@@ -345,7 +347,7 @@ void Automap_EoB::collectCellInfo(int level, uint16 block, int teleporterWallId)
 		}
 	}
 }
-
+*/
 void Automap_EoB::mainLoopProcess(EoBCoreEngine *vm, int inputFlag) {
 	if (!_enabled)
 		return;
@@ -359,15 +361,15 @@ void Automap_EoB::mainLoopProcess(EoBCoreEngine *vm, int inputFlag) {
 			_system->hideOverlay();
 			vm->_sceneUpdateRequired = true;
 		}
-	} else if (_visible && inputFlag == 199) {
+	} /* else if (_visible && inputFlag == 199) {
 		handleClick(vm);
 	} else if (_visible && inputFlag && inputFlag == vm->_keyMap[Common::KEYCODE_n]) {
 		_selectedBlock = vm->_currentBlock;
 		editNote(vm);
-	} else if (_visible && inputFlag &&
+	} */else if (_visible && inputFlag &&
 		(inputFlag == (vm->_keyMap[Common::KEYCODE_UP] | 0x100) || inputFlag == (vm->_keyMap[Common::KEYCODE_DOWN] | 0x100) ||
 			inputFlag == (vm->_keyMap[Common::KEYCODE_LEFT] | 0x100) || inputFlag == (vm->_keyMap[Common::KEYCODE_RIGHT] | 0x100))) {
-		// Shift+arrows move the map selection cursor (place notes without a mouse).
+
 		if (inputFlag == (vm->_keyMap[Common::KEYCODE_UP] | 0x100))
 			moveSelection(0, -1);
 		else if (inputFlag == (vm->_keyMap[Common::KEYCODE_DOWN] | 0x100))
@@ -572,7 +574,7 @@ void Automap_EoB::draw(EoBCoreEngine *vm) {
 			// should still draw as a door.
 			const LevelBlockProperty *bp = &_blockData[block];
 			const uint32 bkey = noteKey(vm->_currentLevel, block);
-			uint8 dbits = _automapDoorBits.contains(bkey) ? _automapDoorBits[bkey] : 0;
+			int8 dbits = _automapDoorBits.contains(bkey) ? _automapDoorBits[bkey] : 0;
 			if ((_wllWallFlags[bp->walls[0]] & 8) || (_wllWallFlags[bp->walls[2]] & 8))
 				dbits |= 1;
 			if ((_wllWallFlags[bp->walls[1]] & 8) || (_wllWallFlags[bp->walls[3]] & 8))
@@ -640,18 +642,18 @@ void Automap_EoB::draw(EoBCoreEngine *vm) {
 				surf.fillRect(Common::Rect(px, py, px + ps, py + ps), pcol);
 			}
 
-			if (_automapIcons.contains(bkey)) {
+			/* if (_automapIcons.contains(bkey)) {
 				const uint8 ic = _automapIcons[bkey];
 				uint32 col = (ic == kAmTeleport) ? cTele : cStair;
 				if (!visited)
 					col = cWallSeen;
 				drawIcon(&surf, sx, sy, cell, ic, col);
-			}
+			}*/
 
-			if (_automapNotes.contains(bkey)) {
+			/* if (_automapNotes.contains(bkey)) {
 				const int ds = MAX(2, cell / 3);
 				surf.fillRect(Common::Rect(sx + cell - ds, sy, sx + cell, sy + ds), cNote);
-			}
+			}*/
 		}
 	}
 
@@ -710,7 +712,7 @@ void Automap_EoB::draw(EoBCoreEngine *vm) {
 				   l1X + (gx - l1X) / 4, l1Y + (gy - l1Y) / 4,
 				   l2X + (gx - l2X) / 4, l2Y + (gy - l2Y) / 4, cParty);
 
-	// Plaque text: area name over coords, in two size-fitted bands so they never overlap.
+
 	if (bigFont) {
 		const uint16 cb = (_selectedBlock != 0xFFFF) ? _selectedBlock : vm->_currentBlock;
 		const Common::String lvl = _levelNames[vm->_currentLevel - 1];
@@ -732,7 +734,7 @@ void Automap_EoB::draw(EoBCoreEngine *vm) {
 	// Footer: the selected cell's note / auto-info (the KEYS panel documents N).
 	if (bigFont) {
 		Common::String foot;
-		if (_editing) {
+		/* if (_editing) {
 			const char *caret = ((_system->getMillis() / 400) & 1) ? "_" : " ";
 			foot = Common::String("Note: ") + _editBuffer + caret;
 		} else if (_selectedBlock != 0xFFFF) {
@@ -750,6 +752,9 @@ void Automap_EoB::draw(EoBCoreEngine *vm) {
 			if (!body.empty())
 				foot = Common::String("Selected: ") + body;
 		}
+
+
+		*/
 		if (!foot.empty()) {
 			const int footH = (L.mapY + L.mapH) - L.footY;
 			const int fpad = MAX(2, footH / 6);
@@ -765,48 +770,6 @@ void Automap_EoB::draw(EoBCoreEngine *vm) {
 	_system->updateScreen();
 }
 
-void Automap_EoB::saveStrings(Common::OutSaveFile *out) const {
-	auto writeStringMap = [&out](const Common::HashMap<uint32, Common::String> &map) {
-		out->writeUint32BE(map.size());
-		for (Common::HashMap<uint32, Common::String>::const_iterator it = map.begin(); it != map.end(); ++it) {
-			out->writeUint32BE(it->_key);
-			out->writeString(it->_value);
-			out->writeByte(0);
-		}
-	};
-
-	writeStringMap(_automapNotes);
-	out->writeUint32BE(_automapIcons.size());
-	for (Common::HashMap<uint32, uint8>::const_iterator it = _automapIcons.begin(); it != _automapIcons.end(); ++it) {
-		out->writeUint32BE(it->_key);
-		out->writeByte(it->_value);
-	}
-	writeStringMap(_automapAutoInfo);
-}
-
-void Automap_EoB::loadStrings(Common::SeekableReadStreamEndianWrapper *in) {
-	_automapNotes.clear();
-	_automapIcons.clear();
-	_automapAutoInfo.clear();
-	_automapDoorBits.clear();
-
-	auto readStringMap = [&in](Common::HashMap<uint32, Common::String> &map) {
-		uint32 n = in->readUint32BE();
-		for (uint32 i = 0; i < n; ++i) {
-			uint32 k = in->readUint32BE();
-			map[k] = in->readString();
-		}
-	};
-
-	readStringMap(_automapNotes);
-	uint32 numIcons = in->readUint32BE();
-	for (uint32 i = 0; i < numIcons; ++i) {
-		uint32 k = in->readUint32BE();
-		_automapIcons[k] = in->readByte();
-	}
-	readStringMap(_automapAutoInfo);
-}
-
 Automap_EoB::AutomapLayout Automap_EoB::createLayout() const {
 	const int ow = _system->getOverlayWidth();
 	const int oh = _system->getOverlayHeight();
@@ -853,93 +816,6 @@ void Automap_EoB::moveSelection(int dx, int dy) {
 	_selectedBlock = (by << 5) | bx;
 }
 
-void Automap_EoB::handleClick(EoBCoreEngine *vm) {
-	const Common::Point p = vm->_eventMan->getMousePos();
-	const AutomapLayout l = createLayout();
-	if (p.x < l.offX || p.y < l.offY)
-		return;
-	const int bx = (p.x - l.offX) / l.cell;
-	const int by = (p.y - l.offY) / l.cell;
-	if (bx < 0 || bx >= 32 || by < 0 || by >= 32)
-		return;
-	const uint16 block = (by << 5) | bx;
-	if (isVisited(block) || isSeen(block))
-		_selectedBlock = block;
-}
-
-void Automap_EoB::editNote(EoBCoreEngine *vm) {
-	uint16 block = _selectedBlock;
-	if (!(isVisited(block) || isSeen(block)))
-		return;
-	_selectedBlock = block;
-
-	Common::Keymap *km = vm->_eventMan->getKeymapper()->getKeymap(vm->kKeymapName);
-	km->setEnabled(false);
-
-	const uint32 key = noteKey(vm->_currentLevel, block);
-	_editBuffer = _automapNotes.contains(key) ? _automapNotes[key] : Common::String();
-	_editing = true;
-
-	const uint kMaxLen = 40;
-	bool done = false, cancel = false;
-	while (!done && !vm->shouldQuit()) {
-		draw(vm);
-		vm->updateInput();
-		for (Common::List<KyraEngine_v1::Event>::const_iterator e = vm->_eventList.begin(); e != vm->_eventList.end(); ++e) {
-			if (e->event.type != Common::EVENT_KEYDOWN)
-				continue;
-			const Common::KeyCode kc = e->event.kbd.keycode;
-			const uint16 asc = e->event.kbd.ascii;
-			if (kc == Common::KEYCODE_RETURN || kc == Common::KEYCODE_KP_ENTER)
-				done = true;
-			else if (kc == Common::KEYCODE_ESCAPE)
-				done = cancel = true;
-			else if (kc == Common::KEYCODE_BACKSPACE) {
-				if (!_editBuffer.empty())
-					_editBuffer.deleteLastChar();
-			} else if (asc >= 32 && asc < 127 && _editBuffer.size() < kMaxLen) {
-				_editBuffer += (char)asc;
-			}
-		}
-		vm->_eventList.clear();
-		_system->delayMillis(12);
-	}
-
-	if (!cancel) {
-		_editBuffer.trim();
-		if (_editBuffer.empty())
-			_automapNotes.erase(key);
-		else
-			_automapNotes[key] = _editBuffer;
-	}
-
-	_editing = false;
-	_editBuffer.clear();
-	km->setEnabled(true);
-}
-
-Common::String Automap_EoB::listItems(EoBCoreEngine *vm, uint16 block) const {
-	if (block >= 1024)
-		return Common::String();
-	Common::String out;
-	const uint16 head = _blockData[block].drawObjects;
-	uint16 o = head;
-	int guard = 0;
-	while (o && guard++ < 64) {
-		const EoBItem *it = &vm->_items[o];
-		const char *nm = vm->_itemNames[it->nameUnid];
-		if (nm && *nm) {
-			if (!out.empty())
-				out += ", ";
-			out += nm;
-		}
-		o = it->next;
-		if (o == head)
-			break;
-	}
-	return out;
-}
-
 uint16 Automap_EoB::calcNewBlockPosition(uint16 block, int8 dir) const {
 	static const int16 blockPosTable[] = {-32, 1, 32, -1};
 	return (block + blockPosTable[dir & 3]) & 0x3FF;
@@ -947,9 +823,6 @@ uint16 Automap_EoB::calcNewBlockPosition(uint16 block, int8 dir) const {
 
 static void automapFillTri(Graphics::Surface &s, int ax, int ay, int bx, int by, int cx, int cy, uint32 color);
 
-// Comma-separated names of the items currently on `block`. "" if none.
-
-
 void Automap_EoB::drawIcon(Graphics::Surface *surf, int sx, int sy, int cell, uint8 icon, uint32 color) const {
 	const int cx = sx + cell / 2;
 	const int cy = sy + cell / 2;
diff --git a/engines/kyra/gui/automap_eob.h b/engines/kyra/gui/automap_eob.h
index bd1e94fb0c9..2ee1940b377 100644
--- a/engines/kyra/gui/automap_eob.h
+++ b/engines/kyra/gui/automap_eob.h
@@ -54,16 +54,10 @@ public:
 	void markSeen(uint16 block, int8 dir);
 	bool isSeen(uint16 block) const;
 
-	void tagTransition(int fromLevel, uint16 fromBlock, int toLevel); // TODO: REMOVE
-	void collectCellInfo(int level, uint16 block, int teleporterWallId); // TODO: REMOVE
-
 	void mainLoopProcess(EoBCoreEngine *vm, int inputFlag); // TODO: REMOVE
 	bool isVisible() const { return _visible; } // TODO: REMOVE
 	void draw(EoBCoreEngine *vm);
 
-	void saveStrings(Common::OutSaveFile *out) const;
-	void loadStrings(Common::SeekableReadStreamEndianWrapper *in);
-
 private:
 	// Geometry shared by drawing and click hit-testing.
 	struct AutomapLayout {
@@ -86,10 +80,6 @@ private:
 	uint32 noteKey(uint lvl, uint16 block) const { return (lvl << 16) | block; }
 
 	void moveSelection(int dx, int dy);
-	void handleClick(EoBCoreEngine *vm);
-	void editNote(EoBCoreEngine *vm);
-
-	Common::String listItems(EoBCoreEngine *vm, uint16 block) const; // TODO: remove
 
 	void drawIcon(Graphics::Surface *surf, int sx, int sy, int cell, uint8 icon, uint32 color) const;
 
@@ -108,13 +98,9 @@ private:
 		kAmTeleport = 3
 	};
 
-	bool _editing;                    // TODO: remove
-	Common::String _editBuffer;            // TODO: remove
+
 	uint16 _selectedBlock; // TODO: remove
-	Common::HashMap<uint32, uint8> _automapIcons; // TODO: remove
-	Common::HashMap<uint32, Common::String> _automapAutoInfo; // TODO: remove
 	Common::HashMap<uint32, uint8> _automapDoorBits; // TODO: remove
-	Common::HashMap<uint32, Common::String> _automapNotes; // TODO: remove
 
 	static const TranslateableStrings _stringTable[];
 	const char * const *_legendStrings;
diff --git a/engines/kyra/gui/saveload_eob.cpp b/engines/kyra/gui/saveload_eob.cpp
index d0e3b864754..89dd3abbf3c 100644
--- a/engines/kyra/gui/saveload_eob.cpp
+++ b/engines/kyra/gui/saveload_eob.cpp
@@ -294,9 +294,6 @@ Common::Error EoBCoreEngine::loadGameState(int slot) {
 		}
 	}
 
-	if (header.version >= 25)
-		_automap->loadStrings(&in);
-
 	loadLevel(_currentLevel, _currentSub);
 	if (_flags.platform == Common::kPlatformFMTowns && _gameToLoad != -1)
 		_screen->setScreenPalette(_screen->getPalette(0));


Commit: 5169f77878eb293bc8ea591dcaba6636673c4599
    https://github.com/scummvm/scummvm/commit/5169f77878eb293bc8ea591dcaba6636673c4599
Author: athrxx (athrxx at scummvm.org)
Date: 2026-08-25T22:51:35+02:00

Commit Message:
KYRA: (EOB automap feature) - make automap modal

- Similar behavior to the automap in LOL and EOBI/SegaCD.
Currently, you could basically play the game on the map,
but without seeing anything. So you could encounter
monsters, fall into a pit, etc. You could even get trapped
in a dialogue sequence and would then have to blindly
navigate through that.
- I have also added support to navigate between levels
via up/down keys (which LOL and EOBI/SegaCD also
allow), but currently there are no clickable buttons for
this.
- The drawing is even more broken than before, but that
is relatively easy to fix and not the objective of this commit.

Changed paths:
    engines/kyra/engine/eobcommon.cpp
    engines/kyra/engine/eobcommon.h
    engines/kyra/engine/kyra_v1.cpp
    engines/kyra/gui/automap_eob.cpp
    engines/kyra/gui/automap_eob.h
    engines/kyra/gui/gui_eob.cpp
    engines/kyra/gui/saveload_eob.cpp
    engines/kyra/resource/staticres_eob.cpp


diff --git a/engines/kyra/engine/eobcommon.cpp b/engines/kyra/engine/eobcommon.cpp
index bece9fac13f..dfff6a4757b 100644
--- a/engines/kyra/engine/eobcommon.cpp
+++ b/engines/kyra/engine/eobcommon.cpp
@@ -385,13 +385,12 @@ Common::KeymapArray EoBCoreEngine::initKeymaps(const Common::String &gameId) {
 
 	// Non-original: the automap overlay (toggle) and its note editor.
 	addKeymapAction(keyMap, "AMAP", _("Toggle automap"), Common::KeyState(Common::KEYCODE_TAB, '\t'), "TAB", "");
-	addKeymapAction(keyMap, "NOTE", _("Edit map note (while map open)"), Common::KeyState(Common::KEYCODE_n, 'n'), "n", "");
+
 	addKeymapAction(keyMap, "INV", _("Open / Close inventory"), Common::KeyState(Common::KEYCODE_i, 'i'), "i", "JOY_X");
 	addKeymapAction(keyMap, "SCE", _("Switch inventory / Character screen"), Common::KeyState(Common::KEYCODE_p, 'p'), "p", "JOY_Y");
 	addKeymapAction(keyMap, "CMP", _("Camp"), Common::KeyState(Common::KEYCODE_c, 'c'), "c", "");
 	addKeymapAction(keyMap, "CSP", _("Cast spell"), Common::KeyState(Common::KEYCODE_SPACE, ' '), "SPACE", "JOY_LEFT_SHOULDER");
-	// TODO: Spell cursor, but this needs more thought, since different
-	// game versions use different keycodes.
+	// TODO: Spell cursor, but this needs more thought, since different game versions use different keycodes.
 	addKeymapAction(keyMap, "SL1", _("Spell level 1"), Common::KeyState(Common::KEYCODE_1, '1'), "1", "");
 	addKeymapAction(keyMap, "SL2", _("Spell level 2"), Common::KeyState(Common::KEYCODE_2, '2'), "2", "");
 	addKeymapAction(keyMap, "SL3", _("Spell level 3"), Common::KeyState(Common::KEYCODE_3, '3'), "3", "");
@@ -586,7 +585,8 @@ Common::Error EoBCoreEngine::init() {
 
 	// Always create this, regardless of whether the launcher option is enabled or not. Otherwise the map would
 	// be incomplete if the option is enabled later on in the game.
-	_automap = new Automap_EoB(_system, &_levelBlockProperties, _wllWallFlags, _flags.gameID, _flags.lang, true);
+	_automap = new Automap_EoB(_system, &_levelBlockProperties, _wllWallFlags, _flags.gameID, _flags.lang, _configAutomap);
+	assert(_automap);
 
 	return Common::kNoError;
 }
@@ -718,6 +718,7 @@ void EoBCoreEngine::registerDefaultSettings() {
 	ConfMan.registerDefault("importOrigSaves", true);
 	if (_flags.gameID == GI_EOB1)
 		ConfMan.registerDefault("npcpatch", false);
+	ConfMan.registerDefault("automap", _flags.platform != Common::kPlatformSegaCD);
 }
 
 void EoBCoreEngine::readSettings() {
@@ -783,11 +784,9 @@ void EoBCoreEngine::runLoop() {
 	while (!shouldQuit() && _runFlag) {
 		uint32 frameEnd = _system->getMillis() + 8;
 		checkPartyStatus(true);
-		// While the map is up, suppress the play-field buttons so clicks hit the map.
-		int inputFlag = checkInput(_automap->isVisible() ? 0 : _activeButtons, true, 0);
-		removeInputTop();
 
-		_automap->mainLoopProcess(this, inputFlag);
+		checkInput(_activeButtons, true, 0);
+		removeInputTop();
 
 		if (!_runFlag)
 			break;
@@ -796,16 +795,8 @@ void EoBCoreEngine::runLoop() {
 		updateScriptTimers();
 		updateWallOfForceTimers();
 
-		bool sceneRedraw = _sceneUpdateRequired && !_sceneShakeCountdown;
-		// On any view change, mark the now-visible blocks "seen" for the map.
-		if (sceneRedraw)
+		if (_sceneUpdateRequired && !_sceneShakeCountdown) {
 			_automap->markSeen(_currentBlock, _currentDirection);
-
-		if (_automap->isVisible()) {
-			// Opaque overlay: skip the dungeon redraw behind it (redrawn on close).
-			_sceneUpdateRequired = false;
-			_automap->draw(this);
-		} else if (sceneRedraw) {
 			drawScene(1);
 		}
 
diff --git a/engines/kyra/engine/eobcommon.h b/engines/kyra/engine/eobcommon.h
index 52ecdcabe0c..b67b9b16b62 100644
--- a/engines/kyra/engine/eobcommon.h
+++ b/engines/kyra/engine/eobcommon.h
@@ -803,6 +803,7 @@ protected:
 	int clickedSceneSpecial(Button *button);
 	int clickedSpellbookAbort(Button *button);
 	int clickedSpellbookScroll(Button *button);
+	int clickedAutomap(Button *button);
 	int clickedButtonReturnIndex(Button *button);
 
 	void gui_processCharPortraitClick(int index);
diff --git a/engines/kyra/engine/kyra_v1.cpp b/engines/kyra/engine/kyra_v1.cpp
index 560d2a08411..1016080aa97 100644
--- a/engines/kyra/engine/kyra_v1.cpp
+++ b/engines/kyra/engine/kyra_v1.cpp
@@ -379,7 +379,7 @@ void KyraEngine_v1::setupKeyMap() {
 		{ KC(F4), 115, 102, 96 },
 		{ KC(F5), 116, 103, 97 },
 		{ KC(F6), 117, 104, 98 },
-		{ KC(TAB), 118, 118, 118 },
+		{ KC(TAB), 16, 16, 16 },
 		{ KC(a), 31, 31, UNKNOWN_KEYCODE },
 		{ KC(b), 50, 50, 66 },
 		{ KC(c), 48, 48, 67 },
diff --git a/engines/kyra/gui/automap_eob.cpp b/engines/kyra/gui/automap_eob.cpp
index f84c54d301e..227dd8d7c74 100644
--- a/engines/kyra/gui/automap_eob.cpp
+++ b/engines/kyra/gui/automap_eob.cpp
@@ -27,11 +27,10 @@
 #include "kyra/engine/kyra_rpg.h"
 #include "kyra/gui/automap_eob.h"
 
-#include "common/hashmap.h"	// TODO: REMOVE
-#include "common/savefile.h" // TODO: REMOVE
 #include "backends/keymapper/keymapper.h"         // TODO: REMOVE
 #include "kyra/engine/eobcommon.h" // TODO: REMOVE
 
+
 namespace Kyra {
 
 static void automapDrawBigString(Graphics::Surface &dst, const Graphics::Font *font,
@@ -174,6 +173,12 @@ const Automap_EoB::TranslateableStrings Automap_EoB::_stringTable[] = {
 			"Note"
 		},
 
+		{
+			"- KEYS -",
+			"Select Level",
+			"Exit"
+		},
+
 		// Area names for the plaque. The games have no level names in their data and the file
 		// names of the graphics and sound data files provide only vague hints. So we hardcode
 		// the level names here. The Sega CD version of EOBI has an automap, but it doesn't use
@@ -223,6 +228,9 @@ const Automap_EoB::TranslateableStrings Automap_EoB::_stringTable[] = {
 		{
 			"", "", "", "", "", "", "", "", ""
 		},
+		{
+			"", "", ""
+		},
 		{
 			{
 				"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ""
@@ -235,8 +243,8 @@ const Automap_EoB::TranslateableStrings Automap_EoB::_stringTable[] = {
 };
 
 Automap_EoB::Automap_EoB(OSystem *system, LevelBlockProperty **blockData, const uint8 *wllFlags, int gameID, int lang, bool featureEnabled) :
-	_system(system), _blockData(*blockData), _wllWallFlags(wllFlags), _enabled(featureEnabled), _visible(false), _selectedBlock(0xFFFF), _automapBg(nullptr),
-	_automapFrame(nullptr), _levelNames(nullptr), _legendStrings(nullptr), _numLevelNames(gameID == GI_EOB1 ? 12 : (gameID == GI_EOB2 ? 16 : 0)) {
+	_system(system), _blockData(*blockData), _wllWallFlags(wllFlags), _enabled(featureEnabled), _visible(false), _automapBg(nullptr), _automapFrame(nullptr),
+		_levelNames(nullptr), _legendStrings(nullptr), _controlStrings(nullptr), _numLevelNames(gameID == GI_EOB1 ? 12 : (gameID == GI_EOB2 ? 16 : 0)) {
 	_automapBg = new Graphics::Surface();
 	_automapFrame = new Graphics::Surface();
 
@@ -254,6 +262,7 @@ Automap_EoB::Automap_EoB(OSystem *system, LevelBlockProperty **blockData, const
 	assert(gameIndex < ARRAYSIZE(_stringTable[0].levelNames));
 
 	_legendStrings = _stringTable[langIndex].legendStrings;
+	_controlStrings = _stringTable[langIndex].controlStrings;
 	_levelNames = _stringTable[langIndex].levelNames[gameIndex];
 }
 
@@ -347,7 +356,7 @@ void Automap_EoB::collectCellInfo(int level, uint16 block, int teleporterWallId)
 		}
 	}
 }
-*/
+
 void Automap_EoB::mainLoopProcess(EoBCoreEngine *vm, int inputFlag) {
 	if (!_enabled)
 		return;
@@ -366,7 +375,7 @@ void Automap_EoB::mainLoopProcess(EoBCoreEngine *vm, int inputFlag) {
 	} else if (_visible && inputFlag && inputFlag == vm->_keyMap[Common::KEYCODE_n]) {
 		_selectedBlock = vm->_currentBlock;
 		editNote(vm);
-	} */else if (_visible && inputFlag &&
+	} else if (_visible && inputFlag &&
 		(inputFlag == (vm->_keyMap[Common::KEYCODE_UP] | 0x100) || inputFlag == (vm->_keyMap[Common::KEYCODE_DOWN] | 0x100) ||
 			inputFlag == (vm->_keyMap[Common::KEYCODE_LEFT] | 0x100) || inputFlag == (vm->_keyMap[Common::KEYCODE_RIGHT] | 0x100))) {
 
@@ -398,11 +407,9 @@ void Automap_EoB::mainLoopProcess(EoBCoreEngine *vm, int inputFlag) {
 		else
 			vm->clickedTurnRightArrow(&dummy);
 	}
-}
+}*/
 
 void Automap_EoB::draw(EoBCoreEngine *vm) {
-	markVisited(vm->_currentBlock);
-
 	const int ow = _system->getOverlayWidth();
 	const int oh = _system->getOverlayHeight();
 	const Graphics::PixelFormat fmt = _system->getOverlayFormat();
@@ -529,18 +536,18 @@ void Automap_EoB::draw(EoBCoreEngine *vm) {
 		}
 
 		cyy += MAX(6, sc * 5);
-		automapDrawBigString(bg, bigFont, "- KEYS -", lx, cyy, colW, cGoldDim, sc);
+		automapDrawBigString(bg, bigFont, _controlStrings[0], lx, cyy, colW, cGoldDim, sc);
 		cyy += fh * sc + MAX(4, sc * 3);
 		bg.hLine(lx, cyy, lx + colW - 1, cStoneHi);
 		bg.hLine(lx, cyy + 1, lx + colW - 1, cStoneEdge);
 		cyy += MAX(6, sc * 4);
 		const int chipPad = MAX(2, sc * 2);
-		const int chipW = (bigFont ? bigFont->getStringWidth("N") * sc : 6 * sc) + chipPad * 2;
+		const int chipW = (bigFont ? bigFont->getStringWidth("Up/Down") * sc : 6 * sc) + chipPad * 2;	// TODO: Use actual up/down arrow symbols. Our font doesn't have these.
 		const int chipH = fh * sc + chipPad;
 		bg.fillRect(Common::Rect(lx, cyy, lx + chipW, cyy + chipH), cPlaqueEd);
 		bg.fillRect(Common::Rect(lx + 1, cyy + 1, lx + chipW - 1, cyy + chipH - 1), cPlaqueBg);
-		automapDrawBigString(bg, bigFont, "N", lx, cyy + chipPad / 2, chipW, cGold, sc);
-		automapDrawBigString(bg, bigFont, "Add note", lx + chipW + MAX(4, sc * 3), cyy + (chipH - fh * sc) / 2,
+		automapDrawBigString(bg, bigFont, "Up/Down", lx, cyy + chipPad / 2, chipW, cGold, sc);			// TODO: see above
+		automapDrawBigString(bg, bigFont, _controlStrings[1], lx + chipW + MAX(4, sc * 3), cyy + (chipH - fh * sc) / 2,
 							 colW - chipW - MAX(4, sc * 3), cPanelTxt, sc, Graphics::kTextAlignLeft);
 	}
 
@@ -573,9 +580,9 @@ void Automap_EoB::draw(EoBCoreEngine *vm) {
 			// Cache the door bits: an open door loses its wall flag mid-animation but
 			// should still draw as a door.
 			const LevelBlockProperty *bp = &_blockData[block];
-			const uint32 bkey = noteKey(vm->_currentLevel, block);
-			int8 dbits = _automapDoorBits.contains(bkey) ? _automapDoorBits[bkey] : 0;
-			if ((_wllWallFlags[bp->walls[0]] & 8) || (_wllWallFlags[bp->walls[2]] & 8))
+			//const uint32 bkey = noteKey(vm->_currentLevel, block);
+			//int8 dbits = _automapDoorBits.contains(bkey) ? _automapDoorBits[bkey] : 0;
+			/* if ((_wllWallFlags[bp->walls[0]] & 8) || (_wllWallFlags[bp->walls[2]] & 8))
 				dbits |= 1;
 			if ((_wllWallFlags[bp->walls[1]] & 8) || (_wllWallFlags[bp->walls[3]] & 8))
 				dbits |= 2;
@@ -586,7 +593,7 @@ void Automap_EoB::draw(EoBCoreEngine *vm) {
 			if (doorNS)
 				wall[0] = wall[2] = false;
 			if (doorEW)
-				wall[1] = wall[3] = false;
+				wall[1] = wall[3] = false;*/
 
 			surf.fillRect(Common::Rect(sx, sy, sx + cell, sy + cell), visited ? cFloor : cFloorSeen);
 			if (visited) {
@@ -603,7 +610,7 @@ void Automap_EoB::draw(EoBCoreEngine *vm) {
 			if (wall[3])
 				surf.fillRect(Common::Rect(sx, sy, sx + wt, sy + cell), wc);
 
-			if (doorNS || doorEW) {
+			/* if (doorNS || doorEW) {
 				const int dt = MAX(2, wt + 1);
 				const int dm = MAX(1, cell / 5);
 				const int dcx = sx + cell / 2, dcy = sy + cell / 2;
@@ -611,7 +618,7 @@ void Automap_EoB::draw(EoBCoreEngine *vm) {
 					surf.fillRect(Common::Rect(sx + dm, dcy - dt / 2, sx + cell - dm, dcy - dt / 2 + dt), cDoor);
 				if (doorEW)
 					surf.fillRect(Common::Rect(dcx - dt / 2, sy + dm, dcx - dt / 2 + dt, sy + cell - dm), cDoor);
-			}
+			}*/
 
 			// Special-wall pip on the carrying side (skip door types 5/6; 10 = niche).
 			for (int d = 0; d < 4; ++d) {
@@ -657,12 +664,12 @@ void Automap_EoB::draw(EoBCoreEngine *vm) {
 		}
 	}
 
-	if (_selectedBlock != 0xFFFF) {
+	/* if (_selectedBlock != 0xFFFF) {
 		const int sx = offX + (_selectedBlock & 0x1F) * cell;
 		const int sy = offY + (_selectedBlock >> 5) * cell;
 		surf.frameRect(Common::Rect(sx, sy, sx + cell, sy + cell), cSel);
 		surf.frameRect(Common::Rect(sx + 1, sy + 1, sx + cell - 1, sy + cell - 1), cSel);
-	}
+	}*/
 
 	const int mx = offX + (vm->_currentBlock & 0x1F) * cell;
 	const int my = offY + (vm->_currentBlock >> 5) * cell;
@@ -714,7 +721,7 @@ void Automap_EoB::draw(EoBCoreEngine *vm) {
 
 
 	if (bigFont) {
-		const uint16 cb = (_selectedBlock != 0xFFFF) ? _selectedBlock : vm->_currentBlock;
+		const uint16 cb = (vm->_currentBlock != 0xFFFF) ? vm->_currentBlock : 0;
 		const Common::String lvl = _levelNames[vm->_currentLevel - 1];
 		const Common::String crd = Common::String::format("X %d   Y %d", cb & 0x1F, cb >> 5);
 		const int pm = MAX(3, sc * 2);
@@ -810,12 +817,6 @@ Automap_EoB::AutomapLayout Automap_EoB::createLayout() const {
 	return l;
 }
 
-void Automap_EoB::moveSelection(int dx, int dy) {
-	const int bx = CLIP<int>((_selectedBlock & 0x1F) + dx, 0, 31);
-	const int by = CLIP<int>((_selectedBlock >> 5) + dy, 0, 31);
-	_selectedBlock = (by << 5) | bx;
-}
-
 uint16 Automap_EoB::calcNewBlockPosition(uint16 block, int8 dir) const {
 	static const int16 blockPosTable[] = {-32, 1, 32, -1};
 	return (block + blockPosTable[dir & 3]) & 0x3FF;
@@ -843,6 +844,112 @@ void Automap_EoB::drawIcon(Graphics::Surface *surf, int sx, int sy, int cell, ui
 	}
 }
 
+int EoBCoreEngine::clickedAutomap(Button *button) {
+	gui_updateControls();
+	removeInputTop();
+	disableSysTimer(2);
+
+	txt()->removePageBreakFlag();
+	_screen->copyRegion(0, 120, 0, 0, 176, 48, 0, Screen_EoB::kCampMenuBackupPage, Screen::CR_NO_P_CHECK);
+
+	for (int i = 0; i < 6; i++) {
+		if (!testCharacter(i, 1))
+			continue;
+		_characters[i].damageTaken = 0;
+		_characters[i].slotStatus[0] = _characters[i].slotStatus[1] = 0;
+		gui_drawCharPortraitWithStats(i);
+	}
+
+	bool update = true;
+	bool firstPage = true;
+	int currentLevel = _currentLevel;
+	int currentSub = _currentSub;
+	int currentBlock = _currentBlock;
+	int currentDirection = _currentDirection;
+	int numLevels = (_flags.gameID == GI_EOB2) ? 16 : 12;
+
+	_automap->markSeen(_currentBlock, _currentDirection);
+	_automap->markVisited(_currentBlock);
+
+	_system->showOverlay();
+
+	for (bool runLoop = true; runLoop && !shouldQuit();) {
+		uint32 frameEnd = _system->getMillis() + 8;
+
+		int inputFlag = checkInput(nullptr, false, 0) & 0x80FF;
+		removeInputTop();
+
+		if (!_system->isOverlayVisible()) // restore overlay if the GMM or the debugger was used on the map screen
+			_system->showOverlay(); 
+
+		int lvl = _currentLevel;
+
+		if (inputFlag == _keyMap[Common::KEYCODE_ESCAPE] || inputFlag == _keyMap[Common::KEYCODE_TAB]) {
+			runLoop = false;
+		} else if (inputFlag == _keyMap[Common::KEYCODE_UP]) {
+			for (int i = lvl; lvl == _currentLevel && i <= numLevels; ++i) {
+				if (_hasTempDataFlags & (1 << (i - 1)))
+					lvl = i;
+			}
+		} else if (inputFlag == _keyMap[Common::KEYCODE_DOWN]) {
+			for (int i = lvl; lvl == _currentLevel && i > 0; --i) {
+				if (_hasTempDataFlags & (1 << (i - 1)))
+					lvl = i;
+			}
+		}
+
+		if (lvl != _currentLevel) {
+			if (firstPage) {
+				completeDoorOperations();
+				generateTempData();
+				firstPage = false;
+			}
+
+			loadLevel(lvl, 0);
+
+			_currentBlock = (currentLevel == lvl) ? currentBlock : 0xFFFF;
+			update = true;
+		}
+
+		if (update)
+			_automap->draw(this);
+
+		delayUntil(frameEnd);
+	}
+
+	if (currentLevel != _currentLevel || currentSub != _currentSub)
+		loadLevel(currentLevel, currentSub);
+
+	_currentBlock = currentBlock;
+	_currentDirection = currentDirection;
+
+	_system->hideOverlay();
+
+	if (_flags.platform == Common::kPlatformSegaCD) {
+		setLevelPalettes(_currentLevel);
+		_screen->sega_selectPalette(-1, 2, true);
+		gui_setupPlayFieldHelperPages(true);
+		gui_drawAllCharPortraitsWithStats();
+	}
+
+	drawScene(0);
+
+	_screen->setCurPage(0);
+	const ScreenDim *dm = _screen->getScreenDim(10);
+	_screen->copyRegion(0, 0, 0, 120, 176, 48, Screen_EoB::kCampMenuBackupPage, 2, Screen::CR_NO_P_CHECK);
+	_screen->copyRegion(dm->sx << 3, dm->sy, dm->sx << 3, dm->sy, dm->w << 3, dm->h, 2, 0, Screen::CR_NO_P_CHECK);
+
+	_screen->updateScreen();
+
+	enableSysTimer(2);
+	advanceTimers(_restPartyElapsedTime);
+	_restPartyElapsedTime = 0;
+
+	checkPartyStatus(true);
+
+	return button->arg;
+}
+
 } // End of namespace Kyra
 
 #endif // ENABLE_EOB
diff --git a/engines/kyra/gui/automap_eob.h b/engines/kyra/gui/automap_eob.h
index 2ee1940b377..5fe84871e2e 100644
--- a/engines/kyra/gui/automap_eob.h
+++ b/engines/kyra/gui/automap_eob.h
@@ -25,7 +25,6 @@
 #if defined(ENABLE_EOB) || defined(ENABLE_LOL)
 
 #include "common/scummsys.h"
-#include "common/hashmap.h" // TODO: REMOVE
 #include "common/str.h" // TODO: REMOVE
 
 namespace Common {
@@ -54,8 +53,6 @@ public:
 	void markSeen(uint16 block, int8 dir);
 	bool isSeen(uint16 block) const;
 
-	void mainLoopProcess(EoBCoreEngine *vm, int inputFlag); // TODO: REMOVE
-	bool isVisible() const { return _visible; } // TODO: REMOVE
 	void draw(EoBCoreEngine *vm);
 
 private:
@@ -72,17 +69,12 @@ private:
 
 	struct TranslateableStrings {
 		const char *const legendStrings[9];
+		const char *const controlStrings[3];
 		const char *const levelNames[2][16];
 	};
 
 	AutomapLayout createLayout() const;
-
-	uint32 noteKey(uint lvl, uint16 block) const { return (lvl << 16) | block; }
-
-	void moveSelection(int dx, int dy);
-
 	void drawIcon(Graphics::Surface *surf, int sx, int sy, int cell, uint8 icon, uint32 color) const;
-
 	uint16 calcNewBlockPosition(uint16 block, int8 dir) const;
 
 	bool _visible;
@@ -98,13 +90,10 @@ private:
 		kAmTeleport = 3
 	};
 
-
-	uint16 _selectedBlock; // TODO: remove
-	Common::HashMap<uint32, uint8> _automapDoorBits; // TODO: remove
-
 	static const TranslateableStrings _stringTable[];
-	const char * const *_legendStrings;
-	const char * const *_levelNames;
+	const char *const *_legendStrings;
+	const char *const *_controlStrings;
+	const char *const *_levelNames;
 	const int _numLevelNames;
 
 	OSystem *_system;
diff --git a/engines/kyra/gui/gui_eob.cpp b/engines/kyra/gui/gui_eob.cpp
index 0f214800119..2b146ae0fb6 100644
--- a/engines/kyra/gui/gui_eob.cpp
+++ b/engines/kyra/gui/gui_eob.cpp
@@ -828,6 +828,9 @@ void EoBCoreEngine::gui_toggleButtons() {
 void EoBCoreEngine::gui_setPlayFieldButtons() {
 	gui_resetButtonList();
 	gui_initButtonsFromList(_updateFlags ? _buttonList2 : _buttonList1);
+		
+	if (_configAutomap)
+		gui_initButton(99);
 }
 
 void EoBCoreEngine::gui_setInventoryButtons() {
@@ -857,7 +860,7 @@ void EoBCoreEngine::gui_initButton(int index, int, int, int) {
 	Button *b = 0;
 	int cnt = 1;
 
-	if ((_flags.gameID == GI_EOB1 && !(_flags.platform == Common::kPlatformSegaCD && index >= 95) && index > 92) || (_flags.gameID == GI_EOB2 && _buttonDefs[index].x == 0x7fff))
+	if ((_flags.gameID == GI_EOB1 && !(_flags.platform == Common::kPlatformSegaCD && index >= 95) && index > 92 && index < 99) || (_flags.gameID == GI_EOB2 && _buttonDefs[index].x == 0x7fff))
 		return;
 
 	if (_activeButtons) {
diff --git a/engines/kyra/gui/saveload_eob.cpp b/engines/kyra/gui/saveload_eob.cpp
index 89dd3abbf3c..93b30b03ec3 100644
--- a/engines/kyra/gui/saveload_eob.cpp
+++ b/engines/kyra/gui/saveload_eob.cpp
@@ -1504,12 +1504,14 @@ const void *EoBCoreEngine::generateAutoMapTempData() {
 }
 
 void EoBCoreEngine::restoreAutoMapTempData(LevelTempData *tmp) {
-	const uint32 *e = reinterpret_cast<const uint32*>(tmp->automapExploreState);
-	if (e == nullptr)
-		return;
-
-	for (int i = 0; i < 1024; i++)
-		_levelBlockProperties[i].direction = (e[i >> 4] >> ((i << 1) & 0x1E)) & 3;
+	const uint32 *e = reinterpret_cast<const uint32 *>(tmp->automapExploreState);
+	if (!(_hasTempDataMapFlags & (1 << (_currentLevel - 1))) || e == nullptr) {
+		for (int i = 0; i < 1024; i++)
+			_levelBlockProperties[i].direction = 0;
+	} else {
+		for (int i = 0; i < 1024; i++)
+			_levelBlockProperties[i].direction = (e[i >> 4] >> ((i << 1) & 0x1E)) & 3;
+	}
 }
 
 void EoBCoreEngine::releaseAutoMapTempData(LevelTempData *tmp) {
diff --git a/engines/kyra/resource/staticres_eob.cpp b/engines/kyra/resource/staticres_eob.cpp
index cfc13d5b3a9..198448d8ad6 100644
--- a/engines/kyra/resource/staticres_eob.cpp
+++ b/engines/kyra/resource/staticres_eob.cpp
@@ -742,7 +742,9 @@ void EoBCoreEngine::initButtonData() {
 
 		{ 101, 96, 0x1100, 248, 152, 64, 14, 65535 },
 		{ 103, 98, 0x1100, 248, 168, 64, 14, 1 },
-		{ 110, 0, 0x1100, 248, 184, 64, 14, 2 }
+		{ 110, 0, 0x1100, 248, 184, 64, 14, 2 },
+
+		{ 16, 0, 0, 0, 0, 0, 0, 0 }
 	};
 
 	_buttonDefs = new EoBGuiButtonDef[ARRAYSIZE(buttonDefs)];
@@ -813,7 +815,7 @@ void EoBCoreEngine::initButtonData() {
 	if (_flags.platform == Common::kPlatformFMTowns) {
 		static const uint16 keyCodesFMTowns[] = {
 			93, 94, 95, 96, 67, 27, 24, 349, 350, 351, 352, 80, 27, 24, 30, 0, 31, 0, 29, 0, 28, 0, 127, 18, 27, 93, 94, 95, 96,
-			49, 50, 51, 52, 53, 93, 94, 95, 96, 60, 62, 32, 353, 354, 97, 98, 27, 27, 97, 98, 97, 98, 54, 49, 50, 51, 52, 53, 27
+			49, 50, 51, 52, 53, 93, 94, 95, 96, 60, 62, 32, 353, 354, 97, 98, 27, 27, 97, 98, 97, 98, 54, 18, 30, 0, 31, 27, 16
 		};
 
 		const uint16 *c = keyCodesFMTowns;
@@ -822,6 +824,7 @@ void EoBCoreEngine::initButtonData() {
 				_buttonDefs[i].keyCode = *c++;
 			if (_buttonDefs[i].keyCode2)
 				_buttonDefs[i].keyCode2 = *c++;
+			assert(c <= &keyCodesFMTowns[ARRAYSIZE(keyCodesFMTowns)]);
 		}
 	}
 
@@ -915,6 +918,7 @@ void EoBCoreEngine::initButtonData() {
 	EOB_CBN(1, clickedSpellbookScroll);
 	EOB_CBI(1, 21);
 	EOB_CBN(3, clickedButtonReturnIndex);
+	EOB_CBN(1, clickedAutomap);
 #undef EOB_CBI
 #undef EOB_CBN
 }


Commit: 94f871c4559d9df72c5f0691aa7d0fbb78046ad5
    https://github.com/scummvm/scummvm/commit/94f871c4559d9df72c5f0691aa7d0fbb78046ad5
Author: athrxx (athrxx at scummvm.org)
Date: 2026-08-25T22:51:35+02:00

Commit Message:
KYRA: (EOB automap feature) - some drawing fixes

- fix drawing of doors
- fix drawing of niches and other clickable wall objects
- fix drawing of stairs and teleporters (also improve teleporter
   icon visibility)
- add drawing of pits, plates and illusion walls
- fix drawing of breakable objects (like the barrels or crumbling
   wall in the EOBII catacombs)

Changed paths:
    engines/kyra/engine/eobcommon.cpp
    engines/kyra/gui/automap_eob.cpp
    engines/kyra/gui/automap_eob.h


diff --git a/engines/kyra/engine/eobcommon.cpp b/engines/kyra/engine/eobcommon.cpp
index dfff6a4757b..b4f38a4d1a3 100644
--- a/engines/kyra/engine/eobcommon.cpp
+++ b/engines/kyra/engine/eobcommon.cpp
@@ -585,7 +585,7 @@ Common::Error EoBCoreEngine::init() {
 
 	// Always create this, regardless of whether the launcher option is enabled or not. Otherwise the map would
 	// be incomplete if the option is enabled later on in the game.
-	_automap = new Automap_EoB(_system, &_levelBlockProperties, _wllWallFlags, _flags.gameID, _flags.lang, _configAutomap);
+	_automap = new Automap_EoB(_system, &_levelBlockProperties, _wllWallFlags, _specialWallTypes, _flags.gameID, _flags.lang, _configAutomap);
 	assert(_automap);
 
 	return Common::kNoError;
@@ -783,8 +783,8 @@ void EoBCoreEngine::runLoop() {
 
 	while (!shouldQuit() && _runFlag) {
 		uint32 frameEnd = _system->getMillis() + 8;
-		checkPartyStatus(true);
 
+		checkPartyStatus(true);
 		checkInput(_activeButtons, true, 0);
 		removeInputTop();
 
@@ -812,6 +812,7 @@ void EoBCoreEngine::runLoop() {
 		snd_updateLevelScore();
 		snd_updateEnvironmentalSfx(0);
 		turnUndeadAuto();
+
 		delayUntil(frameEnd, false, false, true);
 	}
 }
diff --git a/engines/kyra/gui/automap_eob.cpp b/engines/kyra/gui/automap_eob.cpp
index 227dd8d7c74..d1c82e80b99 100644
--- a/engines/kyra/gui/automap_eob.cpp
+++ b/engines/kyra/gui/automap_eob.cpp
@@ -27,7 +27,6 @@
 #include "kyra/engine/kyra_rpg.h"
 #include "kyra/gui/automap_eob.h"
 
-#include "backends/keymapper/keymapper.h"         // TODO: REMOVE
 #include "kyra/engine/eobcommon.h" // TODO: REMOVE
 
 
@@ -48,21 +47,19 @@ static void automapDrawBigString(Graphics::Surface &dst, const Graphics::Font *f
 		return;
 	}
 
-	// The overlay may be 16- or 32-bit; index pixels by byte width.
 	const int bpp = dst.format.bytesPerPixel;
 	if (bpp != 2 && bpp != 4) {
 		font->drawString(&dst, text, x, y, w, color, align);
 		return;
 	}
 
-	// Render onto a zeroed scratch surface so inked pixels are distinguishable.
 	Graphics::Surface tmp;
 	tmp.create(tw, th, dst.format);
 	tmp.fillRect(Common::Rect(0, 0, tw, th), 0);
 	font->drawString(&tmp, text, 0, 0, tw, color, Graphics::kTextAlignLeft);
 
 	const int dw = tw * scale;
-	int dx = x + (w - dw) / 2; // center (default)
+	int dx = x + (w - dw) / 2;
 	if (align == Graphics::kTextAlignLeft)
 		dx = x;
 	else if (align == Graphics::kTextAlignRight)
@@ -73,7 +70,7 @@ static void automapDrawBigString(Graphics::Surface &dst, const Graphics::Font *f
 		const byte *src = (const byte *)tmp.getBasePtr(0, sy);
 		for (int sx = 0; sx < tw; ++sx) {
 			const uint32 c = (bpp == 2) ? ((const uint16 *)src)[sx] : ((const uint32 *)src)[sx];
-			if (c == 0) // transparent background pixel
+			if (c == 0)
 				continue;
 			const int px = dx + sx * scale;
 			const int py = y + sy * scale;
@@ -83,9 +80,7 @@ static void automapDrawBigString(Graphics::Surface &dst, const Graphics::Font *f
 	tmp.free();
 }
 
-// Solid triangle by scanlines (party arrow).
 static void automapFillTri(Graphics::Surface &s, int ax, int ay, int bx, int by, int cx, int cy, uint32 color) {
-	// Sort vertices by y.
 	if (ay > by) {
 		SWAP(ax, bx);
 		SWAP(ay, by);
@@ -112,7 +107,7 @@ static void automapFillTri(Graphics::Surface &s, int ax, int ay, int bx, int by,
 	}
 }
 
-// Sparse two-tone speckle to fake stone/paper grain (overwrites pixels, no blend).
+// Sparse two-tone speckle to fake stone/paper grain
 static void automapNoise(Graphics::Surface &s, int x0, int y0, int w, int h, uint32 cA, uint32 cB, uint32 seed) {
 	if (w <= 0 || h <= 0 || x0 < 0 || y0 < 0 || x0 + w > s.w || y0 + h > s.h)
 		return;
@@ -167,10 +162,13 @@ const Automap_EoB::TranslateableStrings Automap_EoB::_stringTable[] = {
 			"Stairs up",
 			"Stairs down",
 			"Teleporter",
+			"Pit",
+			"Plate",
 			"Door",
-			"Lever",
-			"Niche",
-			"Note"
+			"Illusionary Wall",
+			"Switch",
+			"Interactive Object",
+			"Niche"
 		},
 
 		{
@@ -242,9 +240,9 @@ const Automap_EoB::TranslateableStrings Automap_EoB::_stringTable[] = {
 	}
 };
 
-Automap_EoB::Automap_EoB(OSystem *system, LevelBlockProperty **blockData, const uint8 *wllFlags, int gameID, int lang, bool featureEnabled) :
-	_system(system), _blockData(*blockData), _wllWallFlags(wllFlags), _enabled(featureEnabled), _visible(false), _automapBg(nullptr), _automapFrame(nullptr),
-		_levelNames(nullptr), _legendStrings(nullptr), _controlStrings(nullptr), _numLevelNames(gameID == GI_EOB1 ? 12 : (gameID == GI_EOB2 ? 16 : 0)) {
+Automap_EoB::Automap_EoB(OSystem *system, LevelBlockProperty **blockData, const uint8 *wllFlags, const uint8 *specialWallTypes, int gameID, int lang, bool featureEnabled) : _system(system), _blockData(*blockData),
+	_wllWallFlags(wllFlags), _specialWallTypes(specialWallTypes), _enabled(featureEnabled), _visible(false), _automapBg(nullptr), _automapFrame(nullptr), _specialBlockIDs(nullptr), _levelNames(nullptr),
+		_legendStrings(nullptr), _controlStrings(nullptr), _numLevelNames(gameID == GI_EOB1 ? 12 : (gameID == GI_EOB2 ? 16 : 0)),	_gameSupportsBreakables(gameID == GI_EOB2) {
 	_automapBg = new Graphics::Surface();
 	_automapFrame = new Graphics::Surface();
 
@@ -264,11 +262,26 @@ Automap_EoB::Automap_EoB(OSystem *system, LevelBlockProperty **blockData, const
 	_legendStrings = _stringTable[langIndex].legendStrings;
 	_controlStrings = _stringTable[langIndex].controlStrings;
 	_levelNames = _stringTable[langIndex].levelNames[gameIndex];
+
+	const uint8 teleporter = gameID == GI_EOB1 ? 52 : 44;
+	const uint8 illusion1 = gameID == GI_EOB1 ? 67 : 46;
+	const uint8 illusion2 = gameID == GI_EOB1 ? 64 : 46;
+	const uint8 plate1 = gameID == GI_EOB1 ? 28 : 35;
+	const uint8 plate2 = gameID == GI_EOB1 ? 28 : 36;
+	const uint8 pit = gameID == GI_EOB1 ? 27 : 38;
+	const uint8 stairsUp = 23;
+	const uint8 stairsDown = 24;
+	const uint8 types[] = { teleporter, illusion1, illusion2, stairsUp, stairsDown, pit, plate1, plate2 };
+	uint8 *specialBlockIDs = new uint8[ARRAYSIZE(types)]();
+	memcpy(specialBlockIDs, types, sizeof(types));
+	_specialBlockIDs = specialBlockIDs;
+	_numSpecialBlockIDs = ARRAYSIZE(types);
 }
 
 Automap_EoB::~Automap_EoB() {
 	delete _automapBg;
 	delete _automapFrame;
+	delete[] _specialBlockIDs;
 }
 
 void Automap_EoB::markVisited(uint16 block) {
@@ -284,11 +297,12 @@ bool Automap_EoB::isVisited(uint16 block) const {
 void Automap_EoB::markSeen(uint16 block, int8 dir) {
 	assert(block < 1024);
 
-	// Mark the cells in the line of sight ahead up to the first wall or enemy (but include the block with the enemy).
+	// Mark the cells in the line of sight ahead up to the first wall, door or enemy (but include the block with the enemy).
 	// Also 1 block diagonal to the left/right of the line of sight, to have a bit more realistic view (and not make
 	// players walk around pointlessly just to complete the map). 
 	// First value is the relative direction to move, next is whether to reveal the block or not. We don't reveal the
 	// blocks directly to the left/right of the party (behind line of sight), even if our route passes through them.
+	// We also don't reveal behind doors or destructable block objects.
 	static const int8 traceRoutes[5][4] = {
 		{ -1,  0,  0,  1 },
 		{  0,  0, -1,  1 },
@@ -304,11 +318,31 @@ void Automap_EoB::markSeen(uint16 block, int8 dir) {
 			int8 d = (dir + *r++) & 3;
 			const uint16 nb = calcNewBlockPosition(b, d);
 			bool reveal = *r++;
-			if (!(_wllWallFlags[_blockData[nb].walls[d ^ 2]] & 1) || _blockData[b].flags & 7)
+			uint8 wn = _blockData[nb].walls[d ^ 2];
+
+			bool blockDestructable = false;
+			if (_gameSupportsBreakables && (_blockData[nb].flags & 0x800)) {
+				// Check if it is destructable from multiple sides to determine if we should treat it as
+				// a wall object (like the breakable windows on the temple ground floor in EOBII) or a
+				// block object (like the barrels in the catacombs).
+				int cn = 0;
+				for (int iii = 0; iii < 4; ++iii) {
+					uint8 s = _specialWallTypes[_blockData[nb].walls[iii]];
+					if (s == 8 || s == 9)
+						++cn;
+				}
+				blockDestructable = cn >= 2;
+			}
+			bool blockHasDestructable = (_gameSupportsBreakables && (_specialWallTypes[wn] == 8 || _specialWallTypes[wn] == 9) );
+
+
+			if ((!(_wllWallFlags[wn] & 9) && !blockDestructable) || _blockData[b].flags & 7)
 				break;
 			b = nb;
 			if (reveal)
 				_blockData[b].direction |= 1;
+			if (_wllWallFlags[wn] & 8 || blockHasDestructable)
+				break;
 		}
 	}
 }
@@ -318,97 +352,6 @@ bool Automap_EoB::isSeen(uint16 block) const {
 	return (_blockData[block].direction & 1) != 0;
 }
 
-/*
-void Automap_EoB::tagTransition(int fromLevel, uint16 fromBlock, int toLevel) {
-	// On a script-driven level change, tag the cell we left with a stairs/teleport
-	// glyph and a "to level N" note (deeper level = down).
-	if (fromLevel < 0 || fromLevel >= 20 || fromBlock >= 1024)
-		return;
-	const uint32 k = ((uint32)fromLevel << 16) | fromBlock;
-	uint8 icon;
-	Common::String info;
-	if (toLevel > fromLevel) {
-		icon = kAmStairsDown;
-		info = Common::String::format("Down to level %d", toLevel);
-	} else if (toLevel < fromLevel) {
-		icon = kAmStairsUp;
-		info = Common::String::format("Up to level %d", toLevel);
-	} else {
-		icon = kAmTeleport;
-		info = Common::String::format("Teleport to level %d", toLevel);
-	}
-	//_automapIcons[k] = icon;
-	//_automapAutoInfo[k] = info;
-}
-
-void Automap_EoB::collectCellInfo(int level, uint16 block, int teleporterWallId) {
-	if (level >= 20 || block >= 1024)
-		return;
-	const uint32 k = noteKey(level, block);
-	if (_automapIcons.contains(k)) // already classified (e.g. as stairs)
-		return;
-	for (int d = 0; d < 4; ++d) {
-		if (_blockData[block].walls[d] == teleporterWallId) {
-			_automapIcons[k] = kAmTeleport;
-			if (!_automapAutoInfo.contains(k))
-				_automapAutoInfo[k] = "Teleporter";
-			break;
-		}
-	}
-}
-
-void Automap_EoB::mainLoopProcess(EoBCoreEngine *vm, int inputFlag) {
-	if (!_enabled)
-		return;
-
-	if (inputFlag == vm->_keyMap[Common::KEYCODE_TAB] || (_visible && inputFlag == vm->_keyMap[Common::KEYCODE_ESCAPE])) {
-		_selectedBlock = vm->_currentBlock;
-		_visible = !_visible;
-		if (_visible) {
-			_system->showOverlay(true);
-		} else {
-			_system->hideOverlay();
-			vm->_sceneUpdateRequired = true;
-		}
-	} /* else if (_visible && inputFlag == 199) {
-		handleClick(vm);
-	} else if (_visible && inputFlag && inputFlag == vm->_keyMap[Common::KEYCODE_n]) {
-		_selectedBlock = vm->_currentBlock;
-		editNote(vm);
-	} else if (_visible && inputFlag &&
-		(inputFlag == (vm->_keyMap[Common::KEYCODE_UP] | 0x100) || inputFlag == (vm->_keyMap[Common::KEYCODE_DOWN] | 0x100) ||
-			inputFlag == (vm->_keyMap[Common::KEYCODE_LEFT] | 0x100) || inputFlag == (vm->_keyMap[Common::KEYCODE_RIGHT] | 0x100))) {
-
-		if (inputFlag == (vm->_keyMap[Common::KEYCODE_UP] | 0x100))
-			moveSelection(0, -1);
-		else if (inputFlag == (vm->_keyMap[Common::KEYCODE_DOWN] | 0x100))
-			moveSelection(0, 1);
-		else if (inputFlag == (vm->_keyMap[Common::KEYCODE_LEFT] | 0x100))
-			moveSelection(-1, 0);
-		else
-			moveSelection(1, 0);
-	} else if (_visible && inputFlag &&
-			   (inputFlag == vm->_keyMap[Common::KEYCODE_UP] || inputFlag == vm->_keyMap[Common::KEYCODE_DOWN] ||
-				inputFlag == vm->_keyMap[Common::KEYCODE_LEFT] || inputFlag == vm->_keyMap[Common::KEYCODE_RIGHT] ||
-				inputFlag == vm->_keyMap[Common::KEYCODE_HOME] || inputFlag == vm->_keyMap[Common::KEYCODE_PAGEUP])) {
-		// Movement is normally button-driven, but those are suppressed now, so
-		// dispatch the keys here. The handlers only read button->index, so a dummy is fine.
-		Button dummy;
-		if (inputFlag == vm->_keyMap[Common::KEYCODE_UP])
-			vm->clickedUpArrow(&dummy);
-		else if (inputFlag == vm->_keyMap[Common::KEYCODE_DOWN])
-			vm->clickedDownArrow(&dummy);
-		else if (inputFlag == vm->_keyMap[Common::KEYCODE_LEFT])
-			vm->clickedLeftArrow(&dummy);
-		else if (inputFlag == vm->_keyMap[Common::KEYCODE_RIGHT])
-			vm->clickedRightArrow(&dummy);
-		else if (inputFlag == vm->_keyMap[Common::KEYCODE_HOME])
-			vm->clickedTurnLeftArrow(&dummy);
-		else
-			vm->clickedTurnRightArrow(&dummy);
-	}
-}*/
-
 void Automap_EoB::draw(EoBCoreEngine *vm) {
 	const int ow = _system->getOverlayWidth();
 	const int oh = _system->getOverlayHeight();
@@ -434,13 +377,14 @@ void Automap_EoB::draw(EoBCoreEngine *vm) {
 	const uint32 cWallSeen = fmt.RGBToColor(0x8a, 0x73, 0x4a);
 	const uint32 cDoor = fmt.RGBToColor(0x7a, 0x4a, 0x1c);
 	const uint32 cStair = fmt.RGBToColor(0x3f, 0x7a, 0x3a);
-	const uint32 cTele = fmt.RGBToColor(0x5a, 0x3f, 0x8a);
+	const uint32 cTele = fmt.RGBToColor(0x5f, 0x5f, 0xca);
+	const uint32 cPlate = fmt.RGBToColor(0x9b, 0x6c, 0x2e);
+	const uint32 cPit = fmt.RGBToColor(0x6c, 0x6e, 0x80);
 	const uint32 cLever = fmt.RGBToColor(0x9a, 0x2d, 0x2d);
+	const uint32 cInteractive = fmt.RGBToColor(0x9a, 0x4d, 0xad);
 	const uint32 cNiche = fmt.RGBToColor(0x8a, 0x5a, 0x1c);
-	const uint32 cNote = fmt.RGBToColor(0x9a, 0x6a, 0x14);
 	const uint32 cParty = fmt.RGBToColor(0xa8, 0x28, 0x1c);
 	const uint32 cPartyEdge = fmt.RGBToColor(0x5a, 0x14, 0x0e);
-	const uint32 cSel = fmt.RGBToColor(0x7a, 0x2a, 0x18);
 	const uint32 cPlaqueBg = fmt.RGBToColor(0x1c, 0x1a, 0x16);
 	const uint32 cPlaqueEd = fmt.RGBToColor(0x0d, 0x0c, 0x0a);
 	const uint32 cGold = fmt.RGBToColor(0xf0, 0xc8, 0x50);
@@ -482,7 +426,7 @@ void Automap_EoB::draw(EoBCoreEngine *vm) {
 		const Common::Rect sr(L.sideX, L.sideY, L.sideX + L.sideW, L.sideY + L.sideH);
 		bg.fillRect(sr, cStoneDark);
 		automapNoise(bg, sr.left, sr.top, L.sideW, L.sideH, cStoneHi, cStoneEdge, 0xc2b2ae35u);
-		automapBevel(bg, sr, cStoneEdge, cStoneHi); // sunken: dark top/left
+		automapBevel(bg, sr, cStoneEdge, cStoneHi);
 
 		// Plaque face
 		bg.fillRect(Common::Rect(L.plX, L.plY, L.plX + L.plW, L.plY + L.plH), cPlaqueEd);
@@ -498,35 +442,55 @@ void Automap_EoB::draw(EoBCoreEngine *vm) {
 		bg.hLine(lx, cyy + 1, lx + colW - 1, cStoneEdge);
 		cyy += MAX(6, sc * 4);
 
+		// TODO: Move legend out of these braces and redraw it with only the icons that are actually
+		// needed for the current level map (to make it less crowded, same way it is done for LOL).
 		const int isz = MAX(6, fh * sc);
-		for (int i = 0; i < 8; ++i) {
+		for (int i = 0; i < 11; ++i) {
 			const int ix = lx, iy = cyy;
 			switch (i) {
-			case 0:
+			case 0: // party (up)
 				automapFillTri(bg, ix + isz / 2, iy, ix + isz, iy + isz, ix, iy + isz, cParty);
-				break; // party (up)
-			case 1:
+				break; 
+			case 1: // stairs up
 				automapFillTri(bg, ix, iy + isz, ix + isz, iy + isz, ix + isz / 2, iy, cStair);
-				break; // stairs up
-			case 2:
+				break;
+			case 2: // stairs down
 				automapFillTri(bg, ix, iy, ix + isz, iy, ix + isz / 2, iy + isz, cStair);
-				break; // stairs down
-			case 3:
-				bg.frameRect(Common::Rect(ix, iy, ix + isz, iy + isz), cTele);
-				bg.frameRect(Common::Rect(ix + 1, iy + 1, ix + isz - 1, iy + isz - 1), cTele);
-				break; // teleporter
-			case 4:
+				break;
+			case 3: // teleporter
+				for (int ii = 0; ii < isz / 2 - sc; ++ii) {
+					bg.drawLine(ix + sc + ii, iy + isz / 2 - ii, ix + isz - sc - ii, iy + isz / 2 - ii, cTele);
+					bg.drawLine(ix + sc + ii, iy + isz / 2 + ii, ix + isz - sc - ii, iy + isz / 2 + ii, cTele);
+				}
+				break; 
+			case 4: // pit
+				bg.drawEllipse(ix + sc, iy + sc, ix + isz - sc, iy + isz - sc, cPit, true);
+				break;
+			case 5: // plate
+				bg.drawEllipse(ix + sc, iy + sc, ix + isz - sc, iy + isz - sc, cPlate, true);
+				break; 
+			case 6: // door
 				bg.fillRect(Common::Rect(ix, iy + isz / 3, ix + isz, iy + isz - isz / 3), cDoor);
-				break; // door
-			case 5:
+				break;
+			case 7: { // illusionary wall
+				bg.fillRect(Common::Rect(ix + sc, iy + sc, ix + isz - sc, iy + isz - sc), cFloor);
+				int step = ((isz - 2 * sc) << 8) / 5;
+				for (int g = 0; g < 5; ++g) {
+					bg.drawLine(ix + sc + (step * g >> 8), iy + sc, ix + sc + (step * g >> 8), iy + isz - sc, cWall);
+					bg.drawLine(ix + sc, iy + sc + (step * g >> 8), ix + isz - sc, iy + sc + (step * g >> 8), cWall);
+				}
+			}	break;
+			case 8:
 				bg.fillRect(Common::Rect(ix + isz / 3, iy, ix + isz - isz / 3, iy + isz), cLever);
-				break; // lever
-			case 6:
+				break;
+			case 9:
+				bg.fillRect(Common::Rect(ix + isz / 3, iy, ix + isz - isz / 3, iy + isz), cInteractive);
+				break;
+			case 10:
 				bg.fillRect(Common::Rect(ix + isz / 3, iy, ix + isz - isz / 3, iy + isz), cNiche);
 				break; // niche
 			default:
-				bg.fillRect(Common::Rect(ix, iy, ix + isz, iy + isz), cNote);
-				break; // note
+				break;
 			}
 			const int tx = lx + isz + MAX(4, sc * 2);
 			const int tw = colW - isz - MAX(4, sc * 2);
@@ -551,14 +515,13 @@ void Automap_EoB::draw(EoBCoreEngine *vm) {
 							 colW - chipW - MAX(4, sc * 3), cPanelTxt, sc, Graphics::kTextAlignLeft);
 	}
 
-	// Copy the chrome into the reused surface, then draw dynamics on top.
 	Graphics::Surface &surf = *_automapFrame;
 	surf.copyRectToSurface(*_automapBg, 0, 0, Common::Rect(0, 0, ow, oh));
 
 	const int cell = L.cell;
 	const int offX = L.offX;
 	const int offY = L.offY;
-	const int wt = MAX(2, cell / 5); // wall bar thickness
+	const int wt = MAX(2, cell / 5);
 
 	for (int by = 0; by < 32; ++by) {
 		for (int bx = 0; bx < 32; ++bx) {
@@ -574,26 +537,31 @@ void Automap_EoB::draw(EoBCoreEngine *vm) {
 			bool wall[4];
 			for (int d = 0; d < 4; ++d) {
 				const uint16 nb = calcNewBlockPosition(block, d);
-				wall[d] = !(_wllWallFlags[_blockData[nb].walls[d ^ 2]] & 1);
+				uint8 w = _blockData[nb].walls[d ^ 2];
+				wall[d] = !(_wllWallFlags[w] & 9);
+
+				// Now we check for breakable block objects (like the barrels in the EOB II catacombs) which shouldn't be drawn as walls.
+				if (wall[d] && _gameSupportsBreakables && (_blockData[nb].flags & 0x800)) {
+					// Check if it is accessible/interactive from multiple sides to determine if we should treat it as a wall object (like the
+					// breakable windows on the temple ground floor in EOBII) or a block object (like the barrels in the catacombs).
+					// If it is a wall object we should draw the wall.
+					int cn = 0;
+					for (int iii = 0; iii < 4; ++iii) {
+						uint8 s = _specialWallTypes[_blockData[nb].walls[iii]];
+						if (s == 8 || s == 9)
+							++cn;
+					}
+					wall[d] = cn < 2;
+				}
 			}
 
-			// Cache the door bits: an open door loses its wall flag mid-animation but
-			// should still draw as a door.
 			const LevelBlockProperty *bp = &_blockData[block];
-			//const uint32 bkey = noteKey(vm->_currentLevel, block);
-			//int8 dbits = _automapDoorBits.contains(bkey) ? _automapDoorBits[bkey] : 0;
-			/* if ((_wllWallFlags[bp->walls[0]] & 8) || (_wllWallFlags[bp->walls[2]] & 8))
-				dbits |= 1;
-			if ((_wllWallFlags[bp->walls[1]] & 8) || (_wllWallFlags[bp->walls[3]] & 8))
-				dbits |= 2;
-			if (dbits)
-				_automapDoorBits[bkey] = dbits;
-			const bool doorNS = (dbits & 1) != 0;
-			const bool doorEW = (dbits & 2) != 0;
+			const bool doorNS = ((_wllWallFlags[bp->walls[0]] | _wllWallFlags[bp->walls[2]]) & 8);
+			const bool doorEW = ((_wllWallFlags[bp->walls[1]] | _wllWallFlags[bp->walls[3]]) & 8);
 			if (doorNS)
 				wall[0] = wall[2] = false;
 			if (doorEW)
-				wall[1] = wall[3] = false;*/
+				wall[1] = wall[3] = false;
 
 			surf.fillRect(Common::Rect(sx, sy, sx + cell, sy + cell), visited ? cFloor : cFloorSeen);
 			if (visited) {
@@ -610,7 +578,8 @@ void Automap_EoB::draw(EoBCoreEngine *vm) {
 			if (wall[3])
 				surf.fillRect(Common::Rect(sx, sy, sx + wt, sy + cell), wc);
 
-			/* if (doorNS || doorEW) {
+			if (doorNS || doorEW) {
+				// FIXME?: Doors on the map always look "closed", regardless of whether open oder close.
 				const int dt = MAX(2, wt + 1);
 				const int dm = MAX(1, cell / 5);
 				const int dcx = sx + cell / 2, dcy = sy + cell / 2;
@@ -618,14 +587,37 @@ void Automap_EoB::draw(EoBCoreEngine *vm) {
 					surf.fillRect(Common::Rect(sx + dm, dcy - dt / 2, sx + cell - dm, dcy - dt / 2 + dt), cDoor);
 				if (doorEW)
 					surf.fillRect(Common::Rect(dcx - dt / 2, sy + dm, dcx - dt / 2 + dt, sy + cell - dm), cDoor);
-			}*/
+			}
 
-			// Special-wall pip on the carrying side (skip door types 5/6; 10 = niche).
+			// Interactive/clickable walls (lever, niche, banner, etc.)
 			for (int d = 0; d < 4; ++d) {
-				const uint8 st = vm->_specialWallTypes[bp->walls[d]];
+				uint16 nb = calcNewBlockPosition(block, d);
+				const uint8 st = _specialWallTypes[_blockData[nb].walls[d ^ 2]];
 				if (st == 0 || st == 5 || st == 6)
 					continue;
-				const uint32 pcol = (st == 10) ? cNiche : cLever;
+
+				// At least for EOBI, there are cases where a wall has a clickable type, but there is no
+				// script function assigned to it. We don't want to draw these "fake" triggers.
+				if (st == 2 && _blockData[nb].assignedObjects == 0)
+					continue;
+
+				// Breakable objects are also "interactive" (you can click them and you get a text message),
+				// but it makes more sense to not to draw them here, but in the middle of the block if they're
+				// block objects (like the barrels in the EOB II catacombs). If it's a wall object (like the
+				// breakable windows on the temple ground floor in EOBII) we should draw it here.
+				if (_gameSupportsBreakables && (_blockData[nb].flags & 0x800)) {
+					int cn = 0;
+					// Check if it is accessible/interactive from multiple sides.
+					for (int iii = 0; iii < 4; ++iii) {
+						uint8 s = _specialWallTypes[_blockData[nb].walls[iii]];
+						if (s == 8 || s == 9)
+							++cn;
+					}
+					if (cn >= 2)
+						continue;
+				}
+
+				const uint32 pcol = (st == 10) ? cNiche : (st == 1 || st == 3 || st == 4) ? cLever : cInteractive;
 				const int ps = MAX(2, cell / 4);
 				int px, py;
 				switch (d) {
@@ -649,28 +641,70 @@ void Automap_EoB::draw(EoBCoreEngine *vm) {
 				surf.fillRect(Common::Rect(px, py, px + ps, py + ps), pcol);
 			}
 
-			/* if (_automapIcons.contains(bkey)) {
-				const uint8 ic = _automapIcons[bkey];
-				uint32 col = (ic == kAmTeleport) ? cTele : cStair;
-				if (!visited)
-					col = cWallSeen;
-				drawIcon(&surf, sx, sy, cell, ic, col);
-			}*/
-
-			/* if (_automapNotes.contains(bkey)) {
-				const int ds = MAX(2, cell / 3);
-				surf.fillRect(Common::Rect(sx + cell - ds, sy, sx + cell, sy + ds), cNote);
-			}*/
+			// TODO: The icons could all be pregenerated instead of individually rendering them each time.
+			auto drawIcon = [cInteractive, cFloor, wc, cPlate, cPit, cStair, cTele](Graphics::Surface *sr, int ix, int iy, int cel, uint8 icon) {
+				const int cx = ix + cel / 2;
+				const int cy = iy + cel / 2;
+				const int r = MAX(2, cel / 3);
+				const int margin = r * 4 / 5;
+				const int step = (cel << 8) / 5;
+				switch (icon) {
+				case 0:
+					for (int i = 0; i < margin; ++i) {
+						sr->drawLine(cx - margin + i, cy - i, cx + margin - i, cy - i , cTele);
+						sr->drawLine(cx - margin + i, cy + i, cx + margin - i , cy + i, cTele);
+					}
+					break;
+				case 1:
+				case 2:
+					for (int pos = 0, g = 0; g < 5; ++g, pos += step) {
+						sr->drawLine(ix + (pos >> 8), iy, ix + (pos >> 8), iy + cel, wc);
+						sr->drawLine(ix, iy + (pos >> 8), ix + cel, iy + (pos >> 8), wc);
+					}
+					break;
+				case 3:
+					automapFillTri(*sr, cx - r, cy + r, cx + r, cy + r, cx, cy - r, cStair);
+					break;
+				case 4:
+					automapFillTri(*sr, cx - r, cy - r, cx + r, cy - r, cx, cy + r, cStair);
+					break;
+				case 5:
+					sr->drawEllipse(cx - margin, cy - margin, cx + margin, cy + margin, cPit, true);
+					break;
+				case 6:
+				case 7:
+					sr->drawEllipse(cx - margin, cy - margin, cx + margin, cy + margin, cPlate, true);
+					break;
+				case 9:
+					sr->fillRect(Common::Rect(cx - margin, cy - margin, cx + margin, cy + margin), cInteractive);
+					break;
+				default:
+					break;
+				}
+			};
+
+			 if (_gameSupportsBreakables && (bp->flags & 0x800)) {
+				// Breakable objects (like the barrels or the crumbling wall in the EOBII catacombs)
+				// Check if it is accessible/interactive from multiple sides.
+				int cn = 0;
+				for (int iii = 0; iii < 4; ++iii) {
+					uint8 s = _specialWallTypes[bp->walls[iii]];
+					if (s == 8 || s == 9)
+						++cn;
+				}
+				if (cn >= 2)
+					drawIcon(&surf, sx, sy, cell, 9);
+			} else {
+				// Special blocks: teleporter, illusionary wall, pressure plate, pit, stairs up/down
+				for (int i = 0; i < _numSpecialBlockIDs; ++i) {
+					uint8 s = _specialBlockIDs[i]; // order: teleporter, illusion1, illusion2, stairsUp, stairsDown, pit, plate1, plate2
+					if (bp->walls[0] == s || bp->walls[1] == s || bp->walls[2] == s || bp->walls[3] == s)
+						drawIcon(&surf, sx, sy, cell, i);
+				}
+			}
 		}
 	}
 
-	/* if (_selectedBlock != 0xFFFF) {
-		const int sx = offX + (_selectedBlock & 0x1F) * cell;
-		const int sy = offY + (_selectedBlock >> 5) * cell;
-		surf.frameRect(Common::Rect(sx, sy, sx + cell, sy + cell), cSel);
-		surf.frameRect(Common::Rect(sx + 1, sy + 1, sx + cell - 1, sy + cell - 1), cSel);
-	}*/
-
 	const int mx = offX + (vm->_currentBlock & 0x1F) * cell;
 	const int my = offY + (vm->_currentBlock >> 5) * cell;
 	const int cx = mx + cell / 2;
@@ -728,7 +762,7 @@ void Automap_EoB::draw(EoBCoreEngine *vm) {
 		const int innerW = L.plW - 2 * pm;
 		const int innerH = L.plH - 2 * pm;
 		const int gap = MAX(2, innerH / 12);
-		const int lvlBand = (innerH - gap) * 6 / 10; // level gets ~60%, coords ~40%
+		const int lvlBand = (innerH - gap) * 6 / 10; 
 		const int crdBand = (innerH - gap) - lvlBand;
 		const int lvlSc = automapFit(bigFont, lvl, innerW, MAX(1, lvlBand / fh));
 		const int crdSc = automapFit(bigFont, crd, innerW, MAX(1, crdBand / fh));
@@ -738,30 +772,9 @@ void Automap_EoB::draw(EoBCoreEngine *vm) {
 		automapDrawBigString(surf, bigFont, crd, L.plX, crdY, L.plW, cPanelTxt, crdSc);
 	}
 
-	// Footer: the selected cell's note / auto-info (the KEYS panel documents N).
 	if (bigFont) {
+		// Footer: TODO? We could write something? Or just leave it blank?
 		Common::String foot;
-		/* if (_editing) {
-			const char *caret = ((_system->getMillis() / 400) & 1) ? "_" : " ";
-			foot = Common::String("Note: ") + _editBuffer + caret;
-		} else if (_selectedBlock != 0xFFFF) {
-			const uint32 selKey = noteKey(vm->_currentLevel, _selectedBlock);
-			Common::String body;
-			if (_automapNotes.contains(selKey)) {
-				body = _automapNotes[selKey];
-			} else {
-				if (_automapAutoInfo.contains(selKey))
-					body = _automapAutoInfo[selKey];
-				const Common::String items = listItems(vm, _selectedBlock);
-				if (!items.empty())
-					body = body.empty() ? items : body + " - " + items;
-			}
-			if (!body.empty())
-				foot = Common::String("Selected: ") + body;
-		}
-
-
-		*/
 		if (!foot.empty()) {
 			const int footH = (L.mapY + L.mapH) - L.footY;
 			const int fpad = MAX(2, footH / 6);
@@ -782,8 +795,8 @@ Automap_EoB::AutomapLayout Automap_EoB::createLayout() const {
 	const int oh = _system->getOverlayHeight();
 	AutomapLayout l;
 
-	// Stone frame around the overlay, a side panel on the right, and a parchment map
-	// inset filling the rest; the 32x32 grid is centred with a footer strip below.
+	// Stone frame around the overlay, a side panel on the right,
+	// and a parchment map inset filling the rest
 	l.frame = CLIP<int>(oh / 36, 8, 28);
 	const int gap = MAX(4, l.frame / 2);
 	l.sideW = MAX(150, ow / 4);
@@ -822,28 +835,6 @@ uint16 Automap_EoB::calcNewBlockPosition(uint16 block, int8 dir) const {
 	return (block + blockPosTable[dir & 3]) & 0x3FF;
 }
 
-static void automapFillTri(Graphics::Surface &s, int ax, int ay, int bx, int by, int cx, int cy, uint32 color);
-
-void Automap_EoB::drawIcon(Graphics::Surface *surf, int sx, int sy, int cell, uint8 icon, uint32 color) const {
-	const int cx = sx + cell / 2;
-	const int cy = sy + cell / 2;
-	const int r = MAX(2, cell / 3);
-	switch (icon) {
-	case kAmStairsDown:
-		automapFillTri(*surf, cx - r, cy - r, cx + r, cy - r, cx, cy + r, color);
-		break;
-	case kAmStairsUp:
-		automapFillTri(*surf, cx - r, cy + r, cx + r, cy + r, cx, cy - r, color);
-		break;
-	case kAmTeleport:
-		surf->frameRect(Common::Rect(cx - r, cy - r, cx + r, cy + r), color);
-		surf->frameRect(Common::Rect(cx - r + 1, cy - r + 1, cx + r - 1, cy + r - 1), color);
-		break;
-	default:
-		break;
-	}
-}
-
 int EoBCoreEngine::clickedAutomap(Button *button) {
 	gui_updateControls();
 	removeInputTop();
@@ -868,6 +859,9 @@ int EoBCoreEngine::clickedAutomap(Button *button) {
 	int currentDirection = _currentDirection;
 	int numLevels = (_flags.gameID == GI_EOB2) ? 16 : 12;
 
+	int ow = _system->getOverlayWidth();
+	int oh = _system->getOverlayHeight();
+
 	_automap->markSeen(_currentBlock, _currentDirection);
 	_automap->markVisited(_currentBlock);
 
@@ -876,11 +870,21 @@ int EoBCoreEngine::clickedAutomap(Button *button) {
 	for (bool runLoop = true; runLoop && !shouldQuit();) {
 		uint32 frameEnd = _system->getMillis() + 8;
 
-		int inputFlag = checkInput(nullptr, false, 0) & 0x80FF;
+		int inputFlag = checkInput(nullptr, false, 0) & 0x80FF;	// TODO? We could allow GMM loading on the map screen, but it would require some post-processing
 		removeInputTop();
 
-		if (!_system->isOverlayVisible()) // restore overlay if the GMM or the debugger was used on the map screen
-			_system->showOverlay(); 
+		// restore overlay if the GMM or the debugger has been used on the map screen
+		if (!_system->isOverlayVisible()) {
+			_system->showOverlay();
+			update = true;
+		}
+
+		// redraw map if the window has been resized
+		if (ow != _system->getOverlayWidth() || oh != _system->getOverlayHeight()) {
+			ow = _system->getOverlayWidth();
+			oh = _system->getOverlayHeight();
+			update = true;
+		}
 
 		int lvl = _currentLevel;
 
@@ -911,10 +915,12 @@ int EoBCoreEngine::clickedAutomap(Button *button) {
 			update = true;
 		}
 
-		if (update)
+		if (update) {
 			_automap->draw(this);
+			update = false;
+		}
 
-		delayUntil(frameEnd);
+		delayUntil(frameEnd, false, false, false); // TODO? allow GMM loading on the map screen, seen comment above
 	}
 
 	if (currentLevel != _currentLevel || currentSub != _currentSub)
diff --git a/engines/kyra/gui/automap_eob.h b/engines/kyra/gui/automap_eob.h
index 5fe84871e2e..1e5e7c9d7e5 100644
--- a/engines/kyra/gui/automap_eob.h
+++ b/engines/kyra/gui/automap_eob.h
@@ -25,7 +25,6 @@
 #if defined(ENABLE_EOB) || defined(ENABLE_LOL)
 
 #include "common/scummsys.h"
-#include "common/str.h" // TODO: REMOVE
 
 namespace Common {
 class OutSaveFile;
@@ -45,7 +44,7 @@ class EoBCoreEngine; // TODO: REMOVE
 
 class Automap_EoB {
 public:
-	Automap_EoB(OSystem *system, LevelBlockProperty **blockData, const uint8 *wllFlags, int gameID, int lang, bool featureEnabled);
+	Automap_EoB(OSystem *system, LevelBlockProperty **blockData, const uint8 *wllFlags, const uint8 *specialWallTypes, int gameID, int lang, bool featureEnabled);
 	~Automap_EoB();
 
 	void markVisited(uint16 block);
@@ -68,27 +67,23 @@ private:
 	};
 
 	struct TranslateableStrings {
-		const char *const legendStrings[9];
+		const char *const legendStrings[12];
 		const char *const controlStrings[3];
 		const char *const levelNames[2][16];
 	};
 
 	AutomapLayout createLayout() const;
-	void drawIcon(Graphics::Surface *surf, int sx, int sy, int cell, uint8 icon, uint32 color) const;
 	uint16 calcNewBlockPosition(uint16 block, int8 dir) const;
 
 	bool _visible;
 
 	LevelBlockProperty *&_blockData;
 	const uint8 *const _wllWallFlags;
+	const uint8 *const _specialWallTypes;
+	const uint8 *_specialBlockIDs;
+	int _numSpecialBlockIDs;
 	const bool _enabled;
-
-	enum AutomapIcon {
-		kAmNone = 0,
-		kAmStairsDown = 1,
-		kAmStairsUp = 2,
-		kAmTeleport = 3
-	};
+	const bool _gameSupportsBreakables;
 
 	static const TranslateableStrings _stringTable[];
 	const char *const *_legendStrings;


Commit: 68d9db488f847867c7ad4de2e5bafce013327465
    https://github.com/scummvm/scummvm/commit/68d9db488f847867c7ad4de2e5bafce013327465
Author: athrxx (athrxx at scummvm.org)
Date: 2026-08-25T22:51:35+02:00

Commit Message:
NEWS: move EOB automap line to the right place

Changed paths:
    NEWS.md


diff --git a/NEWS.md b/NEWS.md
index ea9eeedaa4e..241c17c6fd8 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -54,6 +54,7 @@ For a more comprehensive changelog of the latest experimental code, see:
    - Added support for Korean fan-translation of kyra1 (added in 2026.3.0).
    - Restored non-Korean kyra1 games which got broken ("You're missing the 'KYRA.DAT'
      engine data file or it got corrupted..." message).
+   - Added an optional automap overlay for Eye of the Beholder 1 and 2.
 
  Lure:
    - Improved character pathfinding.
@@ -455,7 +456,6 @@ For a more comprehensive changelog of the latest experimental code, see:
      to enable smarter replacing of thrown weapons from inventory.
    - In Eye of the Beholder 1, Ileria and Beohram NPC data fix is now behind
      a separate feature flag and launcher checkbox.
-   - Added an optional automap overlay for Eye of the Beholder 1 and 2.
 
  Lab:
    - Added keymapper support.




More information about the Scummvm-git-logs mailing list