[Scummvm-git-logs] scummvm master -> 43ef42c76405d5854b0d77bb77d3df5d8ae7286b

neuromancer noreply at scummvm.org
Fri Sep 11 06:11:47 UTC 2026


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

Summary:
b8abda6f64 EEM: several fixes for animations in EEM2 (mac cd)
43ef42c764 SCUMM: RA1: correctly render special german character in subtitles


Commit: b8abda6f643f011a08fa9ea63a1b247538007297
    https://github.com/scummvm/scummvm/commit/b8abda6f643f011a08fa9ea63a1b247538007297
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-09-11T08:11:32+02:00

Commit Message:
EEM: several fixes for animations in EEM2 (mac cd)

Changed paths:
    engines/eem/audio.cpp
    engines/eem/clues.cpp
    engines/eem/eem.cpp
    engines/eem/eem.h
    engines/eem/graphics.cpp
    engines/eem/map_ui.cpp
    engines/eem/module.mk
    engines/eem/site.cpp
    engines/eem/site.h
    engines/eem/ui.cpp


diff --git a/engines/eem/audio.cpp b/engines/eem/audio.cpp
index 29c0b6ece2e..2d4b760444a 100644
--- a/engines/eem/audio.cpp
+++ b/engines/eem/audio.cpp
@@ -64,6 +64,7 @@ const MacSndResource kMacSndResources[] = {
 	{ "M-0107SL", 7011 }, { "M-0113SL", 7013 }, { "M-0115SL", 7014 },
 	{ "M-0163SL", 7024 }, { "NEWSCAN",  7003 }, { "NEWSSHRT", 7007 },
 	{ "PHONESL",  7012 }, { "SQUAK2SL", 7019 }, { "THUNDER",  7025 },
+	{ "PHONE1",   1399 },
 };
 
 Common::String macSndNameFromPath(const Common::Path &path) {
@@ -265,6 +266,9 @@ void AudioPlayer::playMacSnd(uint16 resourceId, Audio::SoundHandle &handle,
 	if (!stream)
 		stream = openMacResource(Common::Path("Eagle Eye Mysteries CD"),
 								 MKTAG('s', 'n', 'd', ' '), resourceId);
+	if (!stream && _vm->isLondon())
+		stream = openMacResource(Common::Path("EEM London CD"),
+			MKTAG('s', 'n', 'd', ' '), resourceId);
 	if (!stream) {
 		warning("AudioPlayer: Mac snd resource %u missing", resourceId);
 		return;
diff --git a/engines/eem/clues.cpp b/engines/eem/clues.cpp
index 3d88c85b522..f05194df612 100644
--- a/engines/eem/clues.cpp
+++ b/engines/eem/clues.cpp
@@ -373,22 +373,101 @@ void EEMEngine::doChoosePartner() {
 	}
 }
 
-// EEM2 case-intro animation — `_DoInitClues` @ 1abf:03b3.
+// Mac London CODE 6:324c.
+void EEMEngine::playMacLondonInitCluesAnim(uint16 caseType, const Picture &bg,
+										   bool haveBriefingBg) {
+	const MacSpritePaletteMap palette = getMacSpritePaletteMap();
+	Graphics::ManagedSurface background(screenWidth(), screenHeight(),
+		Graphics::PixelFormat::createFormatCLUT8());
+	background.clear();
+	if (haveBriefingBg)
+		background.simpleBlitFrom(bg.surface);
+	remapMacSurfaceEndpoints(background, palette);
+	Graphics::ManagedSurface base;
+	base.copyFrom(background);
+	byte pal[kPalSize];
+	const bool havePalette = getSitePalette(0x39, pal);
+	byte black[kPalSize] = {};
+	g_system->getPaletteManager()->setPalette(black, 0, 256);
+	const uint16 music[] = { 27, 36, 28, 29, 36 };
+	bool firstFrame = true;
+
+	auto playAnimation = [&](uint id, int x, int y) {
+		Animation anim;
+		if (!_aniArchive.loadAnimation(id, anim) || anim.empty())
+			return;
+		bool skip = false;
+		for (uint i = 0; i < anim.size() && !shouldQuit(); ++i) {
+			Graphics::ManagedSurface frame;
+			frame.copyFrom(base);
+			blitMacAnimFrameAnchored(frame.surfacePtr(), anim[i], x, y, palette);
+			g_system->copyRectToScreen(frame.getPixels(), frame.pitch, 0, 0, frame.w, frame.h);
+			if (firstFrame) {
+				if (havePalette)
+					fadePaletteFromBlack(pal);
+				if (_music && _musicOn && caseType < ARRAYSIZE(music))
+					_music->playMus(music[caseType], true);
+				firstFrame = false;
+			}
+			g_system->updateScreen();
+			if (i + 1 == anim.size())
+				base.copyFrom(frame);
+			const uint32 start = g_system->getMillis();
+			while (!skip && !shouldQuit() &&
+				   g_system->getMillis() - start < animationFramePeriodMs()) {
+				Common::Event event;
+				while (g_system->getEventManager()->pollEvent(event)) {
+					if (event.type == Common::EVENT_QUIT ||
+						event.type == Common::EVENT_RETURN_TO_LAUNCHER)
+						return;
+					if (event.type == Common::EVENT_KEYDOWN ||
+						event.type == Common::EVENT_LBUTTONDOWN)
+						skip = true;
+				}
+				g_system->updateScreen();
+				g_system->delayMillis(10);
+			}
+		}
+	};
+
+	playAnimation(_partner == kPartnerJake ? 0x18 : 0x1c,
+		332, _partner == kPartnerJake ? 120 : 118);
+	if (shouldQuit())
+		return;
+	switch (caseType) {
+	case 0: playAnimation(0x24, 0, 94); break;
+	case 2: playAnimation(0x26, 0, 90); break;
+	case 3: playAnimation(0x27, 0, 77); break;
+	default: break;
+	}
+	if (shouldQuit())
+		return;
+	if (caseType == 1 && _audio)
+		_audio->playVoc(Common::Path("phone1.voc"));
+	base.copyRectToSurface(background.getBasePtr(256, 0), background.pitch,
+		256, 0, 256, screenHeight());
+	const uint partnerAni = caseType == 1
+		? (_partner == kPartnerJake ? 0x17 : 0x1e)
+		: (_partner == kPartnerJake ? 0x19 : 0x1d);
+	playAnimation(partnerAni, 332, _partner == kPartnerJake ? 120 : 119);
+	if (_audio)
+		_audio->stopVoice();
+}
+
+// EEM2 DOS case-intro animation — `_DoInitClues` @ 1abf:03b3.
 void EEMEngine::playLondonInitCluesAnim(uint16 caseType, const Picture &bg,
 										bool haveBriefingBg) {
-	const bool mac = isMacintosh();
-	const uint introAni = mac ? (_partner == kPartnerJake ? 0x18 : 0x1c)
-							  : (_partner == kPartnerJake ? 0x18 : 0x71);
-	const uint introScript = mac ? 0x17 : 0x18;
-	const int kAnchorX = mac ? 0x14c : 0xd2;
-	const int kAnchorY = mac ? (_partner == kPartnerJake ? 0x78 : 0x76)
-							 : 0x3f;
+	if (isMacintosh()) {
+		playMacLondonInitCluesAnim(caseType, bg, haveBriefingBg);
+		return;
+	}
+	const uint introAni = _partner == kPartnerJake ? 0x18 : 0x71;
+	const uint introScript = 0x18;
+	const int kAnchorX = 0xd2;
+	const int kAnchorY = 0x3f;
 	Animation anim;
 	const bool haveAnim =
 		_aniArchive.loadAnimation(introAni, anim) && !anim.empty();
-	MacSpritePaletteMap macPaletteMap = {0x00, 0xFF};
-	if (mac)
-		macPaletteMap = getMacSpritePaletteMap();
 
 	// `_DoInitClues @ 1abf:03b3` registers a SECOND, fixed briefing character
 	// (Nigel) on the LEFT, gated on caseType (jumptable @ CS:0x720):
@@ -423,22 +502,13 @@ void EEMEngine::playLondonInitCluesAnim(uint16 caseType, const Picture &bg,
 				const uint cell =
 					partnerFrameAtTick((uint16)introScript,
 									   (uint)anim.size(), frame * 140);
-				if (mac)
-					blitMacAnimFrameAnchored(scr, anim[cell],
-											 kAnchorX, kAnchorY,
-											 macPaletteMap);
-				else
-					blitAnimFrameAnchored(scr, anim[cell], kAnchorX, kAnchorY);
+				blitAnimFrameAnchored(scr, anim[cell], kAnchorX, kAnchorY);
 			}
 			if (haveNpc) {
 				// NPC frame script = 0x0e (the `_NewAnimation` animId arg).
 				const uint ncell =
 					partnerFrameAtTick(0x0e, (uint)npc.size(), frame * 140);
-				if (mac)
-					blitMacAnimFrameAnchored(scr, npc[ncell], npcX, npcY,
-											 macPaletteMap);
-				else
-					blitAnimFrameAnchored(scr, npc[ncell], npcX, npcY);
+				blitAnimFrameAnchored(scr, npc[ncell], npcX, npcY);
 			}
 			g_system->unlockScreen();
 		}
@@ -751,6 +821,11 @@ void EEMEngine::doInitClues() {
 				   marked);
 		displayClue(briefingClues);
 	}
+	if (isMacintosh() && isLondon()) {
+		_mystery._onSites[0] = 1;
+		stopMusic();
+		fadeCurrentPaletteToBlack();
+	}
 }
 
 void splitLondonPlayerName(const Common::String &displayName,
@@ -945,7 +1020,7 @@ void EEMEngine::displayClue(const byte *clueBlock, uint maxEntries) {
 
 	const uint stride = isLondon() ? 0x54 : 62;
 	const bool mac = isMacintosh();
-	const bool macSolved = isMacCD() && clueBlock == _mystery.solvedClueBlock();
+	const bool macSolved = isMacTalkie() && clueBlock == _mystery.solvedClueBlock();
 	const int sw = screenWidth();
 	const int sh = screenHeight();
 	MacSpritePaletteMap macPaletteMap = {0x00, 0xFF};
@@ -982,7 +1057,7 @@ void EEMEngine::displayClue(const byte *clueBlock, uint maxEntries) {
 	//       5 notebook entries (-1 terminated)
 	//   EEM1 +0x3a / EEM2 +0x4e: KD-anim number (-1 = none)
 	for (uint i = 0; i < count && !shouldQuit(); i++) {
-		if (isMacCD() && _audio)
+		if (isMacTalkie() && _audio)
 			_audio->stopSpool();
 		g_system->copyRectToScreen(bg.getPixels(), bg.pitch, 0, 0, sw, sh);
 		const byte *c = clueBlock + 4 + i * stride;
@@ -1027,7 +1102,7 @@ void EEMEngine::displayClue(const byte *clueBlock, uint maxEntries) {
 		// ClueBlock +2; entries N>0 read (entry-1)+0x3c (last word).
 		const uint16 charX  = READ_LE_UINT16(c + (useP1 ? 4 : 0));
 		// Mac CD adds 7.68 to the portrait Y and rounds to an integer.
-		const int charY = READ_LE_UINT16(c + (useP1 ? 6 : 2)) + (isMacCD() ? 8 : 0);
+		const int charY = READ_LE_UINT16(c + (useP1 ? 6 : 2)) + (isMacTalkie() ? 8 : 0);
 		uint16 charPicId = (i == 0)
 			? READ_LE_UINT16(clueBlock + 2)
 			: READ_LE_UINT16(c - 2);
@@ -1105,7 +1180,7 @@ void EEMEngine::displayClue(const byte *clueBlock, uint maxEntries) {
 				copyY = bubY;
 			}
 
-			if (isMacCD()) {
+			if (isMacTalkie()) {
 				dialogFont.drawMacWordWrapped(&scratch, textX, textY,
 					MAX<int>(8, textW), text, textColor);
 			} else {
diff --git a/engines/eem/eem.cpp b/engines/eem/eem.cpp
index 2ad0385e342..ccb235c372c 100644
--- a/engines/eem/eem.cpp
+++ b/engines/eem/eem.cpp
@@ -322,7 +322,7 @@ EEMEngine::EEMEngine(OSystem *syst, const ADGameDescription *gameDesc)
 	if (gameDesc && gameDesc->gameId &&
 		Common::String(gameDesc->gameId) == "eem2")
 		_variant = kVariantLondonCD;
-	setAnimScripts(isLondon(), isMacCD());
+	setAnimScripts(isLondon(), isMacTalkie());
 	_language = gameDesc ? gameDesc->language : Common::EN_ANY;
 }
 
@@ -576,7 +576,7 @@ Common::Error EEMEngine::run() {
 			warning("Mac FONT resource failed to load; text will not render");
 		if (!loadMacDialogFont(_dialogFont))
 			warning("Mac dialog FONT resource failed to load");
-		if (isMacCD() && !loadMacFontResource(_newspaperFont, kMacSmallFontResource, 9))
+		if (isMacTalkie() && !loadMacFontResource(_newspaperFont, kMacSmallFontResource, 9))
 			warning("Mac newspaper FONT resource failed to load");
 	} else if (!_font.load(Common::Path("FONT.FNT"))) {
 		warning("FONT.FNT failed to load; text will not render");
@@ -2219,17 +2219,25 @@ void EEMEngine::startLondonTravelMusic(uint8 travelKind) {
 		{ 7, 23, 17 },
 		{ 10, 21, 24 },
 	};
+	static const uint16 kMacLondonTravelMusic[4][3] = {
+		{ 0, 0, 0 },
+		{ 22, 25, 7 },
+		{ 23, 17, 10 },
+		{ 21, 24, 35 },
+	};
 	if (!_music || !_musicOn || travelKind == 0 ||
 		travelKind >= ARRAYSIZE(kLondonTravelMusic))
 		return;
 
-	const uint track = kLondonTravelMusic[travelKind][_rng.getRandomNumber(2)];
+	const uint choice = _rng.getRandomNumber(2);
+	const uint track = isMacintosh() ? kMacLondonTravelMusic[travelKind][choice]
+								   : kLondonTravelMusic[travelKind][choice];
 	_music->playMus(track, /* loop= */ false);
 }
 
 void EEMEngine::finishTravelMusic(bool skipped) {
-	// Mac CD fades after the entrance; DOS lets the travel tune finish.
-	if (isMacCD()) {
+	// Mac talkies fade after the animation; EEM1 DOS lets the tune finish.
+	if (isMacTalkie()) {
 		if (_music && _music->isPlaying() && !shouldQuit()) {
 			_music->fadeOut();
 			const uint32 startMs = g_system->getMillis();
diff --git a/engines/eem/eem.h b/engines/eem/eem.h
index d293ed3e3af..c263726f307 100644
--- a/engines/eem/eem.h
+++ b/engines/eem/eem.h
@@ -141,6 +141,7 @@ public:
 	// rather than the single-valued `_variant` (which can only hold one of
 	// them at a time). This lets EEM2 Mac be both London and Macintosh.
 	bool isMacintosh() const { return getPlatform() == Common::kPlatformMacintosh; }
+	bool isMacTalkie() const { return isMacCD() || (isMacintosh() && isLondon()); }
 	bool isDemo() const {
 		return _gameDescription && (_gameDescription->flags & ADGF_DEMO);
 	}
@@ -215,6 +216,7 @@ public:
 	/// EEM2/London `_DoPuzzle @ 2542:1482`. A clue entry can gate the rest of
 	/// itself behind a "check the manual / a real map" puzzle
 	bool doPuzzle(uint puzzleId);
+	bool doMacLondonPuzzle(Common::SeekableReadStream &stream);
 
 	void displayFloppyHotspotDialog(uint siteNum, uint hotIdx);
 
@@ -516,6 +518,8 @@ private:
 	void showLondonCharSelect();
 	void playLondonInitCluesAnim(uint16 caseType, const Picture &bg,
 								 bool haveBriefingBg);
+	void playMacLondonInitCluesAnim(uint16 caseType, const Picture &bg,
+									bool haveBriefingBg);
 	void playCdFloppyInitCluesAnim(uint16 caseType, bool floppy,
 								   const Picture &bg, bool haveBriefingBg);
 
@@ -600,6 +604,7 @@ public:
 	/// EEM2 `_DoTravel @ 1717:0622` transition music. The matrix entry
 	/// (1..3) chooses one of three short one-shot MUS tracks at random.
 	void startLondonTravelMusic(uint8 travelKind);
+	bool playMacLondonTravelAnimation(uint8 travelKind);
 private:
 	static int scaleCoord(int value, int target, int source) {
 		const bool negative = value < 0;
diff --git a/engines/eem/graphics.cpp b/engines/eem/graphics.cpp
index 2eb36fbd15d..fee4139ff41 100644
--- a/engines/eem/graphics.cpp
+++ b/engines/eem/graphics.cpp
@@ -116,7 +116,7 @@ uint EEMEngine::getBalloonLineCapacity(uint16 balloonId, int lineH) const {
 
 	const BalloonInsets &insets = kBalloonInsetTable[idx];
 	int textHeight = (int)insets.indDY - (int)insets.y;
-	if (isMacCD())
+	if (isMacTalkie())
 		textHeight = insets.indDY * kMacScreenHeight / kScreenHeight -
 			insets.y * kMacScreenHeight / kScreenHeight;
 	else if (isMacintosh())
@@ -239,6 +239,9 @@ bool EEMEngine::doPuzzle(uint puzzleId) {
 		return true;
 	}
 
+	if (isMacintosh() && isLondon() && bigEndian)
+		return doMacLondonPuzzle(f);
+
 	const uint16 type = readPuzzleU16(f, bigEndian);
 	const int sw = screenWidth();
 	const int sh = screenHeight();
@@ -813,7 +816,7 @@ uint16 EEMEngine::fitBalloonToText(uint16 bubNum,
 		return bubNum;
 
 	const BalloonInsets &originalInsets = kBalloonInsetTable[originalId];
-	const int lineH = isMacCD() ? 16 : _font.getFontHeight();
+	const int lineH = isMacTalkie() ? 16 : _font.getFontHeight();
 	const uint originalCapacity = getBalloonLineCapacity(originalId, lineH);
 	if (originalCapacity == 0)
 		return bubNum;
@@ -821,7 +824,7 @@ uint16 EEMEngine::fitBalloonToText(uint16 bubNum,
 	Common::Array<Common::String> lines;
 	uint16 insetX, insetY, wrapWidth;
 	getBalloonInsets(originalId, insetX, insetY, wrapWidth);
-	const int wrapW = wrapWidth - (isMacCD() ? 5 : 0);
+	const int wrapW = wrapWidth - (isMacTalkie() ? 5 : 0);
 	_font.wordWrapText(text, MAX<int>(8, wrapW), lines);
 	if (lines.empty())
 		return bubNum;
@@ -872,8 +875,8 @@ bool EEMEngine::getBalloonInsets(uint16 bubNum, uint16 &xInset,
 	const uint idx = bubNum & 0x7F;
 	if (idx >= ARRAYSIZE(kBalloonInsetTable))
 		return false;
-	if (isMacCD()) {
-		// CODE 2:1f74 truncates the fixed-point products.
+	if (isMacTalkie()) {
+		// Mac CD truncates the fixed-point products.
 		xInset = kBalloonInsetTable[idx].x * kMacScreenWidth / kScreenWidth;
 		yInset = kBalloonInsetTable[idx].y * kMacScreenHeight / kScreenHeight;
 		textW = kBalloonInsetTable[idx].w * kMacScreenWidth / kScreenWidth;
diff --git a/engines/eem/map_ui.cpp b/engines/eem/map_ui.cpp
index 3a9c9a71a51..dbf48790555 100644
--- a/engines/eem/map_ui.cpp
+++ b/engines/eem/map_ui.cpp
@@ -121,38 +121,6 @@ void blitBigMapMarker(Graphics::ManagedSurface &dstSurface, const Picture &marke
 	}
 }
 
-void blitMacBigMapPartnerFrame(Graphics::ManagedSurface &dstSurface,
-							   const Picture &frame, int anchorX,
-							   int anchorY) {
-	const byte transp = (byte)(frame.flags >> 8);
-	const int x = anchorX - (int)(int16)frame.miscflags;
-	const int y = anchorY - (int)(int16)frame.rowoff;
-	for (int row = 0; row < frame.surface.h; row++) {
-		const int dstY = y + row;
-		if (dstY < 0 || dstY >= dstSurface.h)
-			continue;
-		const byte *src = (const byte *)frame.surface.getBasePtr(0, row);
-		byte *dst = (byte *)dstSurface.getBasePtr(0, dstY);
-		for (int col = 0; col < frame.surface.w; col++) {
-			const int dstX = x + col;
-			if (dstX < 0 || dstX >= dstSurface.w)
-				continue;
-			const byte color = src[col];
-			if (color != transp) {
-				// The map partner frames are authored against the overview
-				// ColorTable: 0 is white and 0xff is black. The detail-map
-				// ColorTable swaps those endpoints.
-				if (color == 0x00)
-					dst[dstX] = 0xff;
-				else if (color == 0xff)
-					dst[dstX] = 0x00;
-				else
-					dst[dstX] = color;
-			}
-		}
-	}
-}
-
 struct BigMapEntryInfo {
 	uint16 overviewX = 0;
 	uint16 overviewY = 0;
@@ -897,15 +865,17 @@ bool EEMEngine::doLondonApproach(uint16 approachId) {
 	byte palette[768] = {};
 	const bool haveVideo =
 		decodeLondonApproachFirstFrame(data.videoId, base, palette, mac);
+	bool havePalette = haveVideo;
 	if (!haveVideo)
 		base.clear();
 	if (mac) {
+		havePalette = getSitePalette(0x45 + data.videoId, palette) || havePalette;
 		Picture background;
 		const uint16 backgroundPic =
 			kMacLondonApproachBackgroundBasePic + data.videoId;
 		if (_picsArchive.getPicture(backgroundPic, background) &&
 			!background.surface.empty()) {
-			if (haveVideo) {
+			if (havePalette) {
 				const byte black =
 					closestPaletteIndex(palette, 0x00, 0x00, 0x00, 0xfe);
 				remapSurfaceColor(background.surface, 0xff, black);
@@ -954,7 +924,7 @@ bool EEMEngine::doLondonApproach(uint16 approachId) {
 		return -1;
 	};
 	const byte approachTextColor =
-		closestPaletteIndex(haveVideo ? palette : nullptr, 0, 0, 0,
+		closestPaletteIndex(havePalette ? palette : nullptr, 0, 0, 0,
 							mac ? (byte)0xfe : (byte)1);
 
 	auto drawApproachOverlay = [&](Graphics::ManagedSurface &scratch,
@@ -1112,16 +1082,16 @@ bool EEMEngine::doLondonApproach(uint16 approachId) {
 	fadeCurrentPaletteToBlack();
 	CursorMan.showMouse(true);
 	setSiteHotspotCursorId(6);
-	if (_music && _voiceOn)
+	if (_music && _musicOn)
 		_music->playMus(0x27, /* loop= */ true);
 
 	uint page = 0;
 	drawScreen(page);
-	if (haveVideo)
+	if (havePalette)
 		fadePaletteFromBlack(palette);
 	else
 		setSitePalette(0x3b);
-	if (haveVideo) {
+	if (haveVideo && !mac) {
 		playVideo(page);
 		drawScreen(page);
 	}
@@ -1384,8 +1354,8 @@ void EEMEngine::drawBigMapDetail(int scrollX, int scrollY,
 		const int anchorX = mac ? scaleX(0x101) : 0x101;
 		const int anchorY = mac ? scaleY(0x50) : 0x50;
 		if (mac)
-			blitMacBigMapPartnerFrame(scratch, detailAnim[frameIdx],
-									  anchorX, anchorY);
+			blitMacAnimFrameAnchored(scratch.surfacePtr(), detailAnim[frameIdx],
+									 anchorX, anchorY);
 		else
 			blitAnimFrameAnchored(scratch.surfacePtr(),
 								  detailAnim[frameIdx],
diff --git a/engines/eem/module.mk b/engines/eem/module.mk
index 0ea400f8c61..04d47310120 100644
--- a/engines/eem/module.mk
+++ b/engines/eem/module.mk
@@ -9,6 +9,7 @@ MODULE_OBJS = \
 	font.o \
 	graphics.o \
 	installer.o \
+	mac_ui.o \
 	map_ui.o \
 	metaengine.o \
 	music.o \
diff --git a/engines/eem/site.cpp b/engines/eem/site.cpp
index 93550c31ceb..592dd8eee03 100644
--- a/engines/eem/site.cpp
+++ b/engines/eem/site.cpp
@@ -761,11 +761,7 @@ const uint32 kImpatienceDelayMs = 60 * 1000;
 // London variant is active; any seqnum not listed falls through to the shared
 // EEM1 scripts below. Only the seqnums that actually differ are listed.
 //
-// NOTE: EEM2 scripts 0x27/0x2e/0x30 end with a `0x81 N` jump (loop back to
-// entry N) rather than a 0x80 restart. `frameFromScriptAtTick` has no jump
-// support (EEM1 never used it), so the flat frame list is stored: correct for a
-// one-shot play-through, but a looped play replays the intro instead of just
-// the post-jump tail. Acceptable for these site NPC fidgets; revisit if needed.
+// DOS London scripts 0x27/0x2e/0x30 still use flat loops here.
 const uint8 kScript06London[] = {
 	0,1,2,3,4,5,6,7,8,9,10,11,11,11,5,6,7,8,9,10,
 	11,11,11,5,6,7,8,9,10,11,11,11,5,4,3,2,1,0,
@@ -859,19 +855,361 @@ const AnimScriptLong kAnimScriptsLondonLong[] = {
 
 // Select the scripts and timing of the original release.
 bool g_londonAnimScripts = false;
-bool g_macCDAnimScripts = false;
+bool g_macAnimScripts = false;
 
-void setAnimScripts(bool london, bool macCD) {
+void setAnimScripts(bool london, bool macTalkie) {
 	g_londonAnimScripts = london;
-	g_macCDAnimScripts = macCD;
+	g_macAnimScripts = macTalkie;
 }
 
 struct AnimScriptRef {
 	const uint8 *frames;
 	uint16 len;
+	uint16 loopStart;
+
+	constexpr AnimScriptRef(const uint8 *data = nullptr, uint16 count = 0, uint16 start = 0)
+		: frames(data), len(count), loopStart(start) {}
+};
+
+// Mac London animation sequences, including their loop destinations.
+const uint8 kMacLondonScript19[] = {
+	0, 0, 0, 0
+};
+
+const uint8 kMacLondonScript44[] = {
+	8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 0, 1, 2, 3, 4, 5, 6, 7,
+	8
+};
+
+const uint8 kMacLondonScript45[] = {
+	0, 0, 0, 0, 0, 1, 2, 3
+};
+
+const uint8 kMacLondonScript46[] = {
+	33, 33, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,
+	18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 32
+};
+
+const uint8 kMacLondonScript47[] = {
+	19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
+	19, 19, 19, 19, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
+	16, 17, 18, 19, 19
+};
+
+const uint8 kMacLondonScript48[] = {
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
+	0, 0, 0, 0, 0
+};
+
+const uint8 kMacLondonScript49[] = {
+	33, 33, 33, 33, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
+	16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 31, 30, 29,
+	28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9,
+	8, 7, 6, 5, 4, 3, 2, 1, 0
+};
+
+const uint8 kMacLondonScript4a[] = {
+	0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
+	20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 30, 29, 28, 27, 26, 25, 24, 23,
+	22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3,
+	2, 1, 0
+};
+
+const uint8 kMacLondonScript4b[] = {
+	20, 20, 20, 20, 20, 20, 20, 20, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
+	12, 13, 14, 15, 16, 17, 18, 19, 19
+};
+
+const uint8 kMacLondonScript4c[] = {
+	0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+const uint8 kMacLondonScript4d[] = {
+	1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
+	20, 21, 22, 0, 0, 0
 };
+
+const uint8 kMacLondonScript4e[] = {
+	2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2,
+	2, 2, 2, 2, 2, 2, 0, 2
+};
+
+const uint8 kMacLondonScript51[] = {
+	0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19
+};
+
+const uint8 kMacLondonScript53[] = {
+	24, 24, 24, 25, 26, 27, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
+	14, 15, 16, 17, 18, 19, 20, 21, 22, 23
+};
+
+const uint8 kMacLondonScript54[] = {
+	3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+	3, 3, 3, 3, 3, 0, 1, 2
+};
+
+const uint8 kMacLondonScript56[] = {
+	0, 0, 0, 0, 0, 1, 2, 3, 3, 3, 3, 4, 5, 6, 6, 7, 8, 9, 10, 11,
+	12, 13, 14, 15, 16, 17, 18, 19, 20, 20, 20, 20, 20
+};
+
+const uint8 kMacLondonScript57[] = {
+	29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 0, 1, 2, 3, 4, 5, 6, 7,
+	8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27,
+	28, 29
+};
+
+const uint8 kMacLondonScript58[] = {
+	33, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,
+	19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 33
+};
+
+const uint8 kMacLondonScript59[] = {
+	0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
+	20, 21, 22, 23, 24, 25, 25, 25, 25, 25, 25, 24, 23, 22, 21, 21, 22, 23, 24, 25,
+	25, 25, 25, 25, 25, 25, 25, 25, 25
+};
+
+const uint8 kMacLondonScript5a[] = {
+	3, 3, 3, 3, 3, 0, 1, 2, 2, 2, 1, 0, 3, 3, 3, 3, 3, 3, 3, 3,
+	3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 1, 2, 1, 0
+};
+
+const uint8 kMacLondonScript5b[] = {
+	3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 1, 2, 2, 2, 1, 0
+};
+
+const uint8 kMacLondonScript5d[] = {
+	0, 1, 2, 3, 4, 0, 0, 0, 0, 0, 0, 0
+};
+
+const uint8 kMacLondonScript5e[] = {
+	0, 0, 0, 0, 1, 2, 3, 4, 0
+};
+
+const uint8 kMacLondonScript5f[] = {
+	6, 6, 6, 6, 6, 6, 6, 0, 1, 2, 0, 6, 3, 4, 5
+};
+
+const uint8 kMacLondonScript64[] = {
+	12, 12, 12, 12, 12, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 6,
+	6, 6, 6, 5, 5, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11
+};
+
+const uint8 kMacLondonScript65[] = {
+	7, 7, 7, 7, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7,
+	7, 7, 7, 7
+};
+
+const uint8 kMacLondonScript66[] = {
+	10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+	10, 10, 10, 10, 10, 10, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
+};
+
+const uint8 kMacLondonScript67[] = {
+	0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 4, 4, 5, 5, 4, 4, 5, 5,
+	4, 4, 5, 5
+};
+
+const uint8 kMacLondonScript68[] = {
+	0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 2, 1, 0, 1, 0, 1, 0, 1, 2, 1
+};
+
+const uint8 kMacLondonScript6a[] = {
+	15, 0, 1, 2, 3, 4, 5, 5, 5, 5, 5, 5, 6, 7, 8, 9, 10, 11, 12, 13,
+	14, 15, 15, 15, 15
+};
+
+const uint8 kMacLondonScript6c[] = {
+	0, 1, 2, 3, 4, 5, 5, 5, 5, 3, 2, 1, 0, 0, 0, 0, 0, 0
+};
+
+const uint8 kMacLondonScript6d[] = {
+	0, 0, 1, 2, 3, 3, 3, 3, 2, 2, 3, 3, 3, 3, 2, 1, 0, 0, 0
+};
+
+const uint8 kMacLondonScript6e[] = {
+	1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0
+};
+
+const uint8 kMacLondonScript70[] = {
+	1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1
+};
+
+const uint8 kMacLondonScript71[] = {
+	0, 1, 2, 3, 4, 5, 6
+};
+
+const uint8 kMacLondonScript74[] = {
+	0, 1, 1, 0, 1, 0, 2, 3, 4, 5, 6, 0
+};
+
+const uint8 kMacLondonScript75[] = {
+	0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13
+};
+
+const uint8 kMacLondonScript76[] = {
+	3, 3, 3, 4, 0, 1, 2, 3, 3, 3, 4, 0, 1, 2, 3, 3, 3, 3
+};
+
+const uint8 kMacLondonScript77[] = {
+	16, 16, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 16
+};
+
+const uint8 kMacLondonScript78[] = {
+	0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1
+};
+
+const uint8 kMacLondonScript79[] = {
+	28, 28, 29, 29, 30, 30, 31, 31, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5,
+	6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15,
+	16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 23, 24, 24, 25, 25,
+	26, 26, 27, 27
+};
+
+const uint8 kMacLondonScript7a[] = {
+	14, 0, 1, 2, 3, 4, 5, 6, 6, 6, 6, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+	14, 14, 14
+};
+
+const uint8 kMacLondonScript7c[] = {
+	6, 6, 0, 1, 2, 3, 4, 5, 6, 6, 6
+};
+
+const AnimScriptRef kMacLondonAnimScripts[] = {
+	{ kAnimScripts[0].frames, 10, 0 }, // 00
+	{ kAnimScripts[24].frames, 6, 0 }, // 01
+	{ kAnimScripts[2].frames, 26, 0 }, // 02
+	{ kAnimScriptsLondon[1].frames, 15, 0 }, // 03
+	{ kAnimScriptsLondon[2].frames, 23, 0 }, // 04
+	{ kAnimScriptsLondon[3].frames, 11, 0 }, // 05
+	{ kScript06London, 38, 0 }, // 06
+	{ kAnimScripts[7].frames, 10, 0 }, // 07
+	{ kAnimScripts[8].frames, 8, 0 }, // 08
+	{ kAnimScripts[9].frames, 9, 0 }, // 09
+	{ kAnimScripts[0].frames, 10, 0 }, // 0a
+	{ kAnimScripts[24].frames, 6, 0 }, // 0b
+	{ kAnimScriptsLondon[1].frames, 15, 0 }, // 0c
+	{ kAnimScriptsLondon[2].frames, 23, 0 }, // 0d
+	{ kAnimScriptsLondon[7].frames, 22, 0 }, // 0e
+	{ kAnimScripts[9].frames, 9, 0 }, // 0f
+	{ kAnimScripts[2].frames, 26, 0 }, // 10
+	{ kAnimScripts[17].frames, 8, 0 }, // 11
+	{ kAnimScripts[18].frames, 9, 0 }, // 12
+	{ kAnimScripts[17].frames, 8, 0 }, // 13
+	{ kAnimScripts[36].frames, 11, 0 }, // 14
+	{ kAnimScripts[0].frames, 10, 0 }, // 15
+	{ kAnimScripts[0].frames, 10, 0 }, // 16
+	{ kScript17, 30, 0 }, // 17
+	{ kAnimScriptsLondon[9].frames, 17, 0 }, // 18
+	{ kMacLondonScript19, 4, 0 }, // 19
+	{ kScript06London, 38, 0 }, // 1a
+	{ kAnimScriptsLondon[3].frames, 11, 0 }, // 1b
+	{ kAnimScriptsLondon[10].frames, 2, 0 }, // 1c
+	{ kAnimScriptsLondon[10].frames, 2, 0 }, // 1d
+	{ kAnimScriptsLondon[10].frames, 2, 0 }, // 1e
+	{ kAnimScriptsLondon[10].frames, 2, 0 }, // 1f
+	{ kAnimScriptsLondon[10].frames, 2, 0 }, // 20
+	{ kAnimScriptsLondon[10].frames, 2, 0 }, // 21
+	{ kAnimScriptsLondon[10].frames, 2, 0 }, // 22
+	{ kAnimScriptsLondon[10].frames, 2, 0 }, // 23
+	{ kAnimScriptsLondon[10].frames, 2, 0 }, // 24
+	{ kAnimScriptsLondon[10].frames, 2, 0 }, // 25
+	{ kAnimScriptsLondon[10].frames, 2, 0 }, // 26
+	{ kAnimScriptsLondon[10].frames, 2, 0 }, // 27
+	{ kAnimScriptsLondon[10].frames, 2, 0 }, // 28
+	{ kMacLondonScript19, 4, 0 }, // 29
+	{ kAnimScripts[34].frames, 4, 0 }, // 2a
+	{ kAnimScriptsLondon[12].frames, 12, 0 }, // 2b
+	{ kAnimScripts[29].frames, 12, 11 }, // 2c
+	{ kScript1dLondon, 46, 0 }, // 2d
+	{ kScript1eLondon, 33, 0 }, // 2e
+	{ kScript1fLondon, 38, 0 }, // 2f
+	{ kAnimScriptsLondon[14].frames, 19, 0 }, // 30
+	{ kAnimScriptsLondon[10].frames, 2, 0 }, // 31
+	{ kAnimScriptsLondon[16].frames, 10, 0 }, // 32
+	{ kMacLondonScript19, 4, 0 }, // 33
+	{ kAnimScriptsLondon[17].frames, 18, 0 }, // 34
+	{ kScript25London, 59, 0 }, // 35
+	{ kAnimScriptsLondon[18].frames, 20, 0 }, // 36
+	{ kScript27London, 29, 22 }, // 37
+	{ kMacLondonScript19, 4, 0 }, // 38
+	{ kAnimScriptsLondon[20].frames, 14, 0 }, // 39
+	{ kAnimScripts[34].frames, 4, 0 }, // 3a
+	{ kScript2bLondon, 39, 0 }, // 3b
+	{ kAnimScriptsLondon[22].frames, 22, 0 }, // 3c
+	{ kAnimScripts[29].frames, 12, 11 }, // 3d
+	{ kAnimScripts[24].frames, 6, 0 }, // 3e
+	{ kAnimScriptsLondon[25].frames, 26, 25 }, // 3f
+	{ kScript31London, 62, 0 }, // 40
+	{ kAnimScriptsLondon[26].frames, 27, 0 }, // 41
+	{ kAnimScriptsLondon[27].frames, 27, 0 }, // 42
+	{ kAnimScripts[34].frames, 4, 0 }, // 43
+	{ kMacLondonScript44, 21, 0 }, // 44
+	{ kMacLondonScript45, 8, 0 }, // 45
+	{ kMacLondonScript46, 36, 35 }, // 46
+	{ kMacLondonScript47, 45, 44 }, // 47
+	{ kMacLondonScript48, 25, 0 }, // 48
+	{ kMacLondonScript49, 69, 0 }, // 49
+	{ kMacLondonScript4a, 63, 0 }, // 4a
+	{ kMacLondonScript4b, 29, 28 }, // 4b: hold the last frame; original jumps past it.
+	{ kMacLondonScript4c, 35, 0 }, // 4c
+	{ kMacLondonScript4d, 26, 24 }, // 4d
+	{ kMacLondonScript4e, 28, 0 }, // 4e
+	{ kAnimScriptsLondon[1].frames, 15, 0 }, // 4f
+	{ kAnimScripts[33].frames, 5, 0 }, // 50
+	{ kMacLondonScript51, 20, 0 }, // 51
+	{ kAnimScripts[36].frames, 11, 0 }, // 52
+	{ kMacLondonScript53, 30, 0 }, // 53
+	{ kMacLondonScript54, 28, 0 }, // 54
+	{ kAnimScripts[17].frames, 8, 0 }, // 55
+	{ kMacLondonScript56, 33, 29 }, // 56
+	{ kMacLondonScript57, 42, 0 }, // 57
+	{ kMacLondonScript58, 36, 35 }, // 58
+	{ kMacLondonScript59, 49, 10 }, // 59
+	{ kMacLondonScript5a, 34, 0 }, // 5a
+	{ kMacLondonScript5b, 19, 0 }, // 5b
+	{ kMacLondonScript19, 4, 0 }, // 5c
+	{ kMacLondonScript5d, 12, 0 }, // 5d
+	{ kMacLondonScript5e, 9, 0 }, // 5e
+	{ kMacLondonScript5f, 15, 0 }, // 5f
+	{ kMacLondonScript51, 20, 0 }, // 60
+	{ kMacLondonScript51, 20, 0 }, // 61
+	{ kMacLondonScript51, 20, 0 }, // 62
+	{ kMacLondonScript51, 20, 0 }, // 63
+	{ kMacLondonScript64, 35, 0 }, // 64
+	{ kMacLondonScript65, 24, 0 }, // 65
+	{ kMacLondonScript66, 36, 0 }, // 66
+	{ kMacLondonScript67, 24, 0 }, // 67
+	{ kMacLondonScript68, 20, 0 }, // 68
+	{ kAnimScripts[17].frames, 8, 0 }, // 69
+	{ kMacLondonScript6a, 25, 0 }, // 6a
+	{ kMacLondonScript19, 4, 0 }, // 6b
+	{ kMacLondonScript6c, 18, 0 }, // 6c
+	{ kMacLondonScript6d, 19, 0 }, // 6d
+	{ kMacLondonScript6e, 20, 0 }, // 6e
+	{ kAnimScripts[31].frames, 13, 0 }, // 6f
+	{ kMacLondonScript70, 15, 0 }, // 70
+	{ kMacLondonScript71, 7, 0 }, // 71
+	{ kAnimScripts[24].frames, 6, 0 }, // 72
+	{ kAnimScripts[33].frames, 5, 0 }, // 73
+	{ kMacLondonScript74, 12, 0 }, // 74
+	{ kMacLondonScript75, 14, 0 }, // 75
+	{ kMacLondonScript76, 18, 0 }, // 76
+	{ kMacLondonScript77, 20, 0 }, // 77
+	{ kMacLondonScript78, 20, 0 }, // 78
+	{ kMacLondonScript79, 64, 0 }, // 79
+	{ kMacLondonScript7a, 23, 0 }, // 7a
+	{ kAnimScriptsLondon[12].frames, 12, 0 }, // 7b
+	{ kMacLondonScript7c, 11, 0 }, // 7c
+	{ kAnimScripts[17].frames, 8, 0 }, // 7d
+};
+
 AnimScriptRef findAnimScript(uint16 seqnum) {
-	if (g_macCDAnimScripts) {
+	if (g_macAnimScripts && g_londonAnimScripts && seqnum < ARRAYSIZE(kMacLondonAnimScripts))
+		return kMacLondonAnimScripts[seqnum];
+	if (g_macAnimScripts && !g_londonAnimScripts) {
 		for (uint i = 0; i < ARRAYSIZE(kAnimScriptsMacCD); i++) {
 			if (kAnimScriptsMacCD[i].seqnum == seqnum) {
 				AnimScriptRef r;
@@ -922,24 +1260,24 @@ AnimScriptRef findAnimScript(uint16 seqnum) {
 }
 
 uint animationFramePeriodMs() {
-	// Mac CD CODE 2:21c4 advances after nine 60 Hz ticks.
-	return g_macCDAnimScripts ? 150 : 140;
+	// Mac CD CheckFrameRate advances after nine 60 Hz ticks.
+	return g_macAnimScripts ? 150 : 140;
 }
 
 uint frameFromScriptAtTick(const uint8 *frames, uint len,
-								  uint numFrames, uint32 tickMs) {
+								  uint numFrames, uint32 tickMs, uint loopStart = 0) {
 	if (!frames || len == 0)
 		return numFrames > 0 ? (uint)((tickMs / animationFramePeriodMs()) % numFrames) : 0;
-	const uint scriptIdx = (uint)((tickMs / animationFramePeriodMs()) % len);
+	const uint tick = tickMs / animationFramePeriodMs();
+	const uint scriptIdx = tick < len ? tick : loopStart + (tick - len) % (len - loopStart);
 	const uint frame     = frames[scriptIdx];
 	return (numFrames > 0) ? MIN<uint>(frame, numFrames - 1) : 0;
 }
 
-// Looping path of `_UpdateAnimations`: walk the script one entry per
-// `_CheckFrameRate` tick, wrap on 0x80.
+// Advance one entry per frame tick, then follow the loop destination.
 uint partnerFrameAtTick(uint16 seqnum, uint numFrames, uint32 tickMs) {
 	const AnimScriptRef s = findAnimScript(seqnum);
-	return frameFromScriptAtTick(s.frames, s.len, numFrames, tickMs);
+	return frameFromScriptAtTick(s.frames, s.len, numFrames, tickMs, s.loopStart);
 }
 
 uint oneShotFrameAtTick(uint16 seqnum, uint numFrames, uint32 tickMs) {
@@ -1032,6 +1370,8 @@ bool SiteScreen::playLondonTravelAnimation(uint fromSite, uint toSite) {
 				travelKind, fromPic, toPic);
 		return false;
 	}
+	if (_vm->isMacintosh())
+		return _vm->playMacLondonTravelAnimation(travelKind);
 
 	const uint partnerSuffix = (travelKind == 3) ? 0 : _vm->getPartnerIndex();
 	const Common::String name = Common::String::format("TRAVEL%u%u.ANM",
@@ -1077,9 +1417,10 @@ void SiteScreen::enter(uint siteNum, bool resetPartnerMood) {
 	const uint16 approachId = (london && sd) ? READ_LE_UINT16(sd + 2) : 0xffff;
 
 	if (london) {
-		if (playArrival)
+		const bool playApproach = firstVisit && approachId != 0xffff;
+		if (playArrival && !(_vm->isMacintosh() && playApproach))
 			playLondonTravelAnimation(_mystery->_lastSite, siteNum);
-		if (firstVisit && approachId != 0xffff) {
+		if (playApproach) {
 			debugC(1, kDebugSite,
 				   "London approach: site %u first visit, approach %u",
 				   siteNum, approachId);
@@ -1656,7 +1997,7 @@ void SiteScreen::renderStaticDrops(uint siteNum) {
 		Picture pic;
 		if (!_vm->getPics().getPicture(picId, pic))
 			continue;
-		if (_vm->isMacCD())
+		if (_vm->isMacTalkie())
 			blitMacMaskedSurface(screen, pic, x, y);
 		else
 			blitMaskedSurface(screen, pic, x, y);
@@ -1708,7 +2049,7 @@ void SiteScreen::renderAnimatedDrops(uint siteNum, uint32 tickMs) {
 	if (!_mystery || !_vm)
 		return;
 
-	if (_vm->isMacintosh() && !_vm->isMacCD())
+	if (_vm->isMacintosh() && !_vm->isMacTalkie())
 		return;
 
 	if (_vm->isFloppy()) {
@@ -1766,10 +2107,10 @@ void SiteScreen::renderAnimatedDrops(uint siteNum, uint32 tickMs) {
 		Animation anim;
 		if (!_vm->getAni().loadAnimation((uint)animId, anim) || anim.empty())
 			continue;
-		const uint32 elapsed = _vm->isMacCD() ? tickMs - _waitPhaseAnchor : tickMs;
+		const uint32 elapsed = _vm->isMacTalkie() ? tickMs - _waitPhaseAnchor : tickMs;
 		const uint frameIdx = partnerFrameAtTick((uint16)animId,
 												  (uint)anim.size(), elapsed);
-		if (_vm->isMacCD())
+		if (_vm->isMacTalkie())
 			blitMacAnimFrameAnchored(screen, anim[frameIdx], x, y);
 		else
 			blitAnimFrameAnchored(screen, anim[frameIdx], x, y);
@@ -2369,8 +2710,8 @@ bool EEMEngine::loadKdAnim(uint16 num, Animation &anim, int &px, int &py,
 	}
 	const uint partner = (_partner == kPartnerJake) ? 0 : 1;
 	animId = kdTable[num][partner];
-	px     = (int)kdTable[num][2 + partner];
-	py     = (int)kdTable[num][4 + partner];
+	px     = (int16)kdTable[num][2 + partner];
+	py     = (int16)kdTable[num][4 + partner];
 
 	if (!_aniArchive.loadAnimation(animId, anim) || anim.empty()) {
 		warning("loadKdAnim(%u): anim %u failed to load", num, animId);
diff --git a/engines/eem/site.h b/engines/eem/site.h
index 3b7b214f770..544e1a0e734 100644
--- a/engines/eem/site.h
+++ b/engines/eem/site.h
@@ -48,7 +48,7 @@ uint oneShotFrameAtTick(uint16 seqnum, uint numFrames, uint32 tickMs);
 /// Total time for one full play of a one-shot gesture (frame count * period).
 uint32 oneShotDurationMs(uint16 seqnum, uint numFrames);
 
-void setAnimScripts(bool london, bool macCD);
+void setAnimScripts(bool london, bool macTalkie);
 uint animationFramePeriodMs();
 
 /// bigMapPartnerFrameAtTick: overview-map partner walk. EEM1 (11-frame anim):
diff --git a/engines/eem/ui.cpp b/engines/eem/ui.cpp
index de7851710a6..94c5cba287a 100644
--- a/engines/eem/ui.cpp
+++ b/engines/eem/ui.cpp
@@ -152,17 +152,19 @@ int scalePdaAnchor(int value, int target, int source) {
 
 void blitPdaPartner(Graphics::ManagedSurface &dst, DBDArchive &aniArchive,
 					uint8 partner, const PdaPartnerSpec &spec,
-					uint32 tickMs, bool mac = false, bool macCD = false) {
+					uint32 tickMs, bool mac = false, bool macCD = false, bool london = false) {
+	const bool native = macCD || (mac && london);
 	const PdaPartnerSpec nativeSpec { 0x02, 0x02, 0x10,
-		(spec.scriptId == 0x02 && partner == kPartnerJenny) ? 4 : 7, 152 };
-	const PdaPartnerSpec &activeSpec = macCD ? nativeSpec : spec;
+		(!london && spec.scriptId == 0x02 && partner == kPartnerJenny) ? 4 : 7,
+		(london && partner == kPartnerJake) ? 150 : 152 };
+	const PdaPartnerSpec &activeSpec = native ? nativeSpec : spec;
 	Animation ani;
 	if (const Picture *fr = partnerFrameFor(aniArchive, partner, activeSpec,
 											tickMs, ani)) {
 		if (mac) {
-			const int anchorX = macCD ? activeSpec.anchorX :
+			const int anchorX = native ? activeSpec.anchorX :
 				scalePdaAnchor(spec.anchorX, kMacScreenWidth, kScreenWidth);
-			const int anchorY = macCD ? activeSpec.anchorY :
+			const int anchorY = native ? activeSpec.anchorY :
 				scalePdaAnchor(spec.anchorY, kMacScreenHeight, kScreenHeight);
 			blitMacAnimFrameAnchored(dst.surfacePtr(), *fr, anchorX,
 									 anchorY);
@@ -300,7 +302,7 @@ constexpr int kMacTravisTextInsetX = 5;
 constexpr int kMacTravisTextInsetY = 4;
 
 bool usesTravisScrollBar(const EEMEngine *vm) {
-	return vm && (vm->isMacCD() || (vm->isMacintosh() && vm->isLondon()));
+	return vm && vm->isMacTalkie();
 }
 
 class TravisScrollBar {
@@ -1757,7 +1759,7 @@ int EEMEngine::doShowEnding(uint num, bool firstPage) {
 	CursorMan.showMouse(true);
 
 	const bool floppyEnding = isFloppy();
-	const bool macCDEnding = isMacCD() && macLooseEnding;
+	const bool macCDEnding = isMacTalkie() && macLooseEnding;
 	const int sw = screenWidth();
 	const int sh = screenHeight();
 	const Common::Rect endingPrevRect = macCDEnding ? Common::Rect(0, 20, 43, 384) :
@@ -1826,7 +1828,7 @@ int EEMEngine::doShowEnding(uint num, bool firstPage) {
 		return 0;
 
 	const bool showFirstTryBadge =
-		num < sizeof(_mysteriesSolved) && _mysteriesSolved[num] == 2 &&
+		!isLondon() && num < sizeof(_mysteriesSolved) && _mysteriesSolved[num] == 2 &&
 		(floppyEnding || ConfMan.getBool("restored_content"));
 	Picture firstTryBadge;
 	const bool haveFirstTryBadge =
@@ -1908,13 +1910,13 @@ int EEMEngine::doShowEnding(uint num, bool firstPage) {
 				x2 = textRect.right;
 
 				Picture bg;
-				if (macCDEnding) {
+				if (isMacCD() && macCDEnding) {
 					if (_picsArchive.getPicture(kFloppyEndingBackgroundPic, bg))
 						blitTravisBackground(scratch, bg, true);
 					if (_picsArchive.getPicture(picNum, bg))
 						blitMacMaskedSurface(scratch.surfacePtr(), bg, 92, 34);
 				} else if (_picsArchive.getPicture(picNum, bg)) {
-					scratch.simpleBlitFrom(bg.surface);
+					blitTravisBackground(scratch, bg, macEnding);
 				}
 				raw = (const char *)buf.data() + off + 10;
 			}
@@ -2045,7 +2047,7 @@ void EEMEngine::doShowScrapbook(uint stage) {
 		return;
 	const bool currentTier = (stage == _chainStage);
 
-	if (isLondon() && _music && _voiceOn)
+	if (isLondon() && _music && _musicOn)
 		_music->playMus(0x5d, /* loop= */ true);
 
 	int mystery = lo;
@@ -2829,7 +2831,7 @@ void EEMEngine::doCaseSelection() {
 		return;
 	}
 
-	if (isLondon() && _music && _voiceOn)
+	if (isLondon() && _music && _musicOn)
 		_music->playMus(2, /* loop= */ true);
 
 	const uint listLen = MIN<uint>((uint)names.size(), stageHi - stageLo + 1);
@@ -3079,7 +3081,7 @@ void EEMEngine::doNotebook() {
 	Common::Point mouse = g_system->getEventManager()->getMousePos();
 	setInteractiveMouseCursor(notebookButtonAt(this, mouse.x, mouse.y));
 
-	if (isLondon() && _music && _voiceOn) {
+	if (isLondon() && _music && _musicOn) {
 		while (notebookFromSite && _music->isPlaying() && !shouldQuit()) {
 			Common::Event drain;
 			while (g_system->getEventManager()->pollEvent(drain)) {}
@@ -3290,7 +3292,7 @@ void EEMEngine::drawNotebookFrame(int &page, TravisScrollBar *scrollBar) {
 		blitTravisBackground(scratch, frame, isMacintosh());
 
 	blitPdaPartner(scratch, _aniArchive, _partner, kPdaNotebookPartner,
-				   g_system->getMillis(), isMacintosh(), isMacCD());
+				   g_system->getMillis(), isMacintosh(), isMacCD(), isLondon());
 
 	// `_DrawNotes` walks `_NoteIndex` for current page; word-wraps each
 	// found clue in `_NotebookRect`. Selected = color 0x3c.
@@ -3413,7 +3415,7 @@ void EEMEngine::doGallery() {
 	Picture galBg;
 	const bool haveBg = _picsArchive.getPicture(0x3f, galBg);
 
-	if (isLondon() && _music && _voiceOn)
+	if (isLondon() && _music && _musicOn)
 		_music->playMus(5, /* loop= */ true);
 
 	const uint8 num = _mystery.numSuspects();
@@ -3645,7 +3647,7 @@ bool EEMEngine::moreInfo(const byte *gd, uint suspectIdx,
 			blitTravisBackground(ms, galBg, mac);
 
 		blitPdaPartner(ms, _aniArchive, _partner, kPdaGalleryPartner,
-					   g_system->getMillis(), mac, isMacCD());
+					   g_system->getMillis(), mac, isMacCD(), isLondon());
 		Picture detail;
 		if (_picsArchive.getPicture(detailPic, detail)) {
 			const Common::Point detailPos =
@@ -3919,7 +3921,7 @@ void EEMEngine::drawGalleryFrame(const byte *gd, uint8 numSuspects,
 		blitTravisBackground(scratch, galBg, mac);
 
 	blitPdaPartner(scratch, _aniArchive, _partner, kPdaGalleryPartner,
-				   g_system->getMillis(), mac, isMacCD());
+				   g_system->getMillis(), mac, isMacCD(), isLondon());
 
 	const bool floppy = isFloppy();
 	const bool compactGallery = floppy || _mystery.usesCompactMacData();
@@ -3948,7 +3950,7 @@ void EEMEngine::drawGalleryFrame(const byte *gd, uint8 numSuspects,
 				continue;
 
 			const int placeX = s.x;
-			const int placeY = isMacCD() ? s.y + 138 - portrait.surface.h :
+			const int placeY = isMacTalkie() ? s.y + 138 - portrait.surface.h :
 				(mac ? s.y : s.y + (0x48 - portrait.surface.h));
 			const int w = MIN<int>(portrait.surface.w, sw - placeX);
 			const int h = MIN<int>(portrait.surface.h, sh - placeY);
@@ -3964,7 +3966,7 @@ void EEMEngine::drawGalleryFrame(const byte *gd, uint8 numSuspects,
 			slotRects[i] = Common::Rect(placeX, placeY,
 										 placeX + w, placeY + h);
 			slotSuspect[i] = (int)i;
-		} else if (!isMacCD()) {
+		} else if (!isMacTalkie()) {
 			// Undiscovered placeholder — small framed "?" box.
 			const int phW = mac ? 0x72 : 0x40;
 			const int phH = mac ? 0x90 : 0x48;
@@ -4072,7 +4074,7 @@ void EEMEngine::accuseDrawScreen(const AccuseNotesCtx &ctx) {
 
 	blitPdaPartner(scratch, _aniArchive, _partner,
 				   isMacCD() ? kPdaNotebookPartner : kPdaGalleryPartner,
-				   g_system->getMillis(), mac, isMacCD());
+				   g_system->getMillis(), mac, isMacCD(), isLondon());
 
 	Common::Array<Common::Rect> &slotRects = *ctx.slotRects;
 	Common::Array<uint> &slotClues = *ctx.slotClues;
@@ -4127,7 +4129,7 @@ void EEMEngine::accuseDrawScreen(const AccuseNotesCtx &ctx) {
 		: (remaining == 1 ? "clue" : "clues");
 	const Common::String counter =
 		Common::String::format("%u %s", remaining, clueWord);
-	if (isMacCD()) {
+	if (isMacTalkie()) {
 		_font.drawString(&scratch, Common::String::format("%u", remaining), 334, 23, 16, 0x23);
 		_font.drawString(&scratch, clueWord, 350, 23, 120, 0x0f);
 	} else {
@@ -4376,7 +4378,7 @@ bool EEMEngine::doAccuseNotes() {
 							break;
 						_mystery._noteSelected[clueId] =
 							_mystery._noteSelected[clueId] ? 0 : 1;
-						if (isMacCD() && _mystery._noteSelected[clueId] && selected + 1 == expected) {
+						if (isMacTalkie() && _mystery._noteSelected[clueId] && selected + 1 == expected) {
 							accuseDrawScreen(ctx);
 							return true;
 						}
@@ -4428,10 +4430,10 @@ void EEMEngine::drawKDBalloonOverCurrentScreen(Common::String text) {
 		_balloonArchive.size() > (bubNum & 0x7F) &&
 		_balloonArchive.loadEntry(bubNum & 0x7F, balloon);
 
-	const int balloonX = isMacCD() ? 100 : scaleX(0x21);
-	const int topBandH = isMacCD() ? 155 : scaleY(0x50);
-	const int centeredMaxH = isMacCD() ? 155 : scaleY(0x4e);
-	int balloonY = isMacCD() ? 1 : scaleY(1);
+	const int balloonX = isMacTalkie() ? 100 : scaleX(0x21);
+	const int topBandH = isMacTalkie() ? 155 : scaleY(0x50);
+	const int centeredMaxH = isMacTalkie() ? 155 : scaleY(0x4e);
+	int balloonY = isMacTalkie() ? 1 : scaleY(1);
 	if (haveBalloon && balloon.surface.h < centeredMaxH)
 		balloonY = (topBandH - balloon.surface.h) / 2;
 
@@ -4457,7 +4459,7 @@ void EEMEngine::drawKDBalloonOverCurrentScreen(Common::String text) {
 	if (dialogFont.isLoaded()) {
 		const byte textColor =
 			mac ? macPaletteMap.black : (haveBalloon ? 0 : 0xF);
-		if (isMacCD())
+		if (isMacTalkie())
 			dialogFont.drawMacWordWrapped(&ms, balloonX + tx, balloonY + ty,
 									  tw, text, textColor);
 		else
@@ -4545,8 +4547,8 @@ void EEMEngine::doAccuse() {
 			else
 				_audio->sayKDDigital(entryKdIdx, entryKDSpeak, _partner);
 		}
-		waitForInput(isMacCD() ? 0xFFFFFFFFu : 60000);
-		if (isMacCD())
+		waitForInput(isMacTalkie() ? 0xFFFFFFFFu : 60000);
+		if (isMacTalkie())
 			interruptAudio();
 	}
 	if (shouldQuit())
@@ -4569,7 +4571,7 @@ void EEMEngine::doAccuse() {
 
 	const byte *gd = _mystery.galleryData();
 
-	if (isLondon() && _music && _voiceOn)
+	if (isLondon() && _music && _musicOn)
 		_music->playMus(4, /* loop= */ true);
 
 	// `_DoAccuse @ 1df2:0c11` outer loop; ESC → NextScreen=3.
@@ -4623,8 +4625,8 @@ void EEMEngine::doAccuse() {
 		if (_audio && kdIdx)
 			_audio->sayKDDigital(kdIdx, 3, _partner);
 
-		waitForInput(isMacCD() ? 0xFFFFFFFFu : 20000);
-		if (isMacCD() && _audio)
+		waitForInput(isMacTalkie() ? 0xFFFFFFFFu : 20000);
+		if (isMacTalkie() && _audio)
 			_audio->stopSpool();
 		_nextScreen = _lastScreen != kScreenInvalid
 						? (ScreenId)_lastScreen : kScreenSite;
@@ -4648,7 +4650,7 @@ void EEMEngine::doAccuse() {
 			Common::String hint =
 				parseString(raw ? raw : "", _playerName, _partner);
 			if (!hint.empty()) {
-				if (isMacCD()) {
+				if (isMacTalkie()) {
 					Picture background;
 					Graphics::ManagedSurface scratch(screenWidth(), screenHeight(),
 						Graphics::PixelFormat::createFormatCLUT8());
@@ -4656,7 +4658,7 @@ void EEMEngine::doAccuse() {
 					if (_picsArchive.getPicture(0x1a7, background))
 						blitTravisBackground(scratch, background, true);
 					blitPdaPartner(scratch, _aniArchive, _partner, kPdaNotebookPartner,
-						g_system->getMillis(), true, true);
+						g_system->getMillis(), true, true, isLondon());
 					copyToScreen(scratch);
 				} else {
 					drawAccuseGallery(num, gd, /* highlighted= */ -1,
@@ -4665,8 +4667,8 @@ void EEMEngine::doAccuse() {
 				drawKDBalloonOverCurrentScreen(hint);
 				if (_audio)
 					_audio->sayKDDigital(kdIdx, 4, _partner);
-				waitForInput(isMacCD() ? 0xFFFFFFFFu : 8000);
-				if (isMacCD() && _audio)
+				waitForInput(isMacTalkie() ? 0xFFFFFFFFu : 8000);
+				if (isMacTalkie() && _audio)
 					_audio->stopSpool();
 			}
 		}
@@ -4674,7 +4676,7 @@ void EEMEngine::doAccuse() {
 	if (shouldQuit())
 		return;
 
-	if (isLondon() && _music && _voiceOn)
+	if (isLondon() && _music && _musicOn)
 		_music->playMus(33, /* loop= */ true);
 
 	drawAccuseGallery(num, gd, highlighted, slotRects, slotSuspect);
@@ -4691,7 +4693,7 @@ void EEMEngine::doAccuse() {
 	while (picked < 0 && !shouldQuit()) {
 		Common::Event ev;
 		while (g_system->getEventManager()->pollEvent(ev)) {
-			if (isMacCD()) {
+			if (isMacTalkie()) {
 				if (ev.type == Common::EVENT_KEYDOWN &&
 					(ev.kbd.keycode == Common::KEYCODE_LEFT || ev.kbd.keycode == Common::KEYCODE_RIGHT)) {
 					ev.kbd.flags = ev.kbd.keycode == Common::KEYCODE_LEFT ? Common::KBD_SHIFT : 0;
@@ -4778,10 +4780,10 @@ void EEMEngine::doAccuse() {
 
 	// DisplayAlibi: DOS 1df2:0145, Mac CD CODE 7:043e.
 	if (!guessedRight) {
-		const bool macCD = isMacCD();
-		const int sw = macCD ? screenWidth() : kScreenWidth;
-		const int sh = macCD ? screenHeight() : kScreenHeight;
-		const MacSpritePaletteMap palette = macCD ? getMacSpritePaletteMap() :
+		const bool macTalkie = isMacTalkie();
+		const int sw = macTalkie ? screenWidth() : kScreenWidth;
+		const int sh = macTalkie ? screenHeight() : kScreenHeight;
+		const MacSpritePaletteMap palette = macTalkie ? getMacSpritePaletteMap() :
 			MacSpritePaletteMap{0x00, 0xFF};
 		static const uint16 kAlibiBubbles[16] = {
 			0x002B, 0x002C, 0x002D, 0x002E,
@@ -4822,9 +4824,9 @@ void EEMEngine::doAccuse() {
 			_balloonArchive.size() > (bubNum & 0x7F) &&
 			_balloonArchive.loadEntry(bubNum & 0x7F, balloon);
 
-		int balloonX = macCD ? 100 : 0x21;
+		int balloonX = macTalkie ? 100 : 0x21;
 		int balloonY = 1;
-		int py = macCD ? 173 : 0x5a;
+		int py = macTalkie ? 173 : 0x5a;
 		if (bindx < 8) {
 			const int bw = haveBalloon ? balloon.surface.w : 0;
 			const int bh = haveBalloon ? balloon.surface.h : 0;
@@ -4833,12 +4835,12 @@ void EEMEngine::doAccuse() {
 				balloonY = (py - bh) / 2;
 			} else {
 				balloonY = 1;
-				if (!macCD)
+				if (!macTalkie)
 					py = bh;
 			}
 		} else {
 			const int bh = haveBalloon ? balloon.surface.h : 0;
-			balloonY = macCD ? MAX(1, (155 - bh) / 2) :
+			balloonY = macTalkie ? MAX(1, (155 - bh) / 2) :
 				((bh < 0x4f) ? (0x50 - bh) / 2 : 1);
 		}
 
@@ -4846,9 +4848,9 @@ void EEMEngine::doAccuse() {
 			Graphics::PixelFormat::createFormatCLUT8());
 		base.clear();
 		if (haveAlibiBg)
-			blitTravisBackground(base, alibiBg, macCD);
+			blitTravisBackground(base, alibiBg, macTalkie);
 		if (haveSuspect) {
-			if (macCD)
+			if (macTalkie)
 				blitMacMaskedSurface(base.surfacePtr(), suspect, 208, py);
 			else
 				base.transBlitFrom(suspect.surface, Common::Point(0x82, py),
@@ -4859,9 +4861,9 @@ void EEMEngine::doAccuse() {
 			Graphics::PixelFormat::createFormatCLUT8());
 		scratch.simpleBlitFrom(base);
 		if (haveBalloon) {
-			if (macCD)
+			if (macTalkie)
 				blitMacMaskedSurface(scratch.surfacePtr(), balloon, balloonX, balloonY,
-					(bubNum & 0x80) != 0, palette);
+					!isLondon() && (bubNum & 0x80) != 0, palette);
 			else
 				scratch.transBlitFrom(balloon.surface, Common::Point(balloonX, balloonY),
 					(uint32)(byte)(balloon.flags >> 8));
@@ -4870,7 +4872,7 @@ void EEMEngine::doAccuse() {
 		uint16 tx = 5, ty = 4, tw = 155;
 		getBalloonInsets(bubNum, tx, ty, tw);
 		if (_font.isLoaded() && !alibi.empty()) {
-			if (macCD)
+			if (macTalkie)
 				_font.drawMacWordWrapped(&scratch, balloonX + tx, balloonY + ty,
 					tw, alibi, palette.black);
 			else
@@ -4879,10 +4881,10 @@ void EEMEngine::doAccuse() {
 									  haveBalloon ? 0 : 0xF);
 		}
 		blitPdaPartner(scratch, _aniArchive, _partner,
-					   macCD ? kPdaNotebookPartner : kPdaGalleryPartner,
-					   g_system->getMillis(), macCD, macCD);
+					   macTalkie ? kPdaNotebookPartner : kPdaGalleryPartner,
+					   g_system->getMillis(), macTalkie, macTalkie, isLondon());
 
-		if (!macCD && _music && _voiceOn) {
+		if (!macTalkie && _music && _voiceOn) {
 			_music->playMus(6, /* loop= */ false);
 			const uint32 musStart = g_system->getMillis();
 			bool aborted = false;
@@ -4909,9 +4911,11 @@ void EEMEngine::doAccuse() {
 		}
 
 		copyToScreen(scratch);
-		if (_audio && gd && (!isMacintosh() || macCD)) {
+		if (macTalkie && isLondon() && _music && _musicOn)
+			_music->playMus(32, false);
+		if (_audio && gd && (!isMacintosh() || macTalkie)) {
 			const uint16 alibiVoice =
-				READ_LE_UINT16(gd + (uint)picked * 0x46 + (macCD ? 4 : 0));
+				READ_LE_UINT16(gd + (uint)picked * 0x46 + (macTalkie ? 4 : 0));
 			const uint16 jakeVoice =
 				READ_LE_UINT16(gd + (uint)picked * 0x46 + 0x06);
 			const uint16 talk =
@@ -4919,9 +4923,11 @@ void EEMEngine::doAccuse() {
 			if (talk != 0 && talk != 0xFFFF)
 				_audio->spoolSound((uint)(talk - 1));
 		}
-		waitForInput(macCD ? 0xFFFFFFFFu : 60000);
-		if (macCD && _audio)
+		waitForInput(macTalkie ? 0xFFFFFFFFu : 60000);
+		if (macTalkie && _audio)
 			_audio->stopSpool();
+		if (macTalkie && isLondon())
+			stopMusic();
 		if (shouldQuit())
 			return;
 
@@ -4944,14 +4950,14 @@ void EEMEngine::doAccuse() {
 				const bool haveR =
 					_balloonArchive.size() > (rBub & 0x7F) &&
 					_balloonArchive.loadEntry(rBub & 0x7F, rBalloon);
-				const int rX = macCD ? 100 : 0x21;
+				const int rX = macTalkie ? 100 : 0x21;
 				int rY = 1;
-				if (haveR && rBalloon.surface.h < (macCD ? 155 : 0x4e))
-					rY = ((macCD ? 155 : 0x50) - rBalloon.surface.h) / 2;
+				if (haveR && rBalloon.surface.h < (macTalkie ? 155 : 0x4e))
+					rY = ((macTalkie ? 155 : 0x50) - rBalloon.surface.h) / 2;
 
 				scratch.simpleBlitFrom(base);
 				if (haveR) {
-					if (macCD)
+					if (macTalkie)
 						blitMacMaskedSurface(scratch.surfacePtr(), rBalloon, rX, rY);
 					else
 						scratch.transBlitFrom(rBalloon.surface, Common::Point(rX, rY),
@@ -4960,7 +4966,7 @@ void EEMEngine::doAccuse() {
 				uint16 rtx = 5, rty = 4, rtw = 155;
 				getBalloonInsets(rBub, rtx, rty, rtw);
 				if (_font.isLoaded()) {
-					if (macCD)
+					if (macTalkie)
 						_font.drawMacWordWrapped(&scratch, rX + rtx, rY + rty,
 							rtw, react, palette.black);
 					else
@@ -4969,15 +4975,15 @@ void EEMEngine::doAccuse() {
 											  haveR ? 0 : 0xF);
 				}
 				blitPdaPartner(scratch, _aniArchive, _partner,
-							   macCD ? kPdaNotebookPartner : kPdaGalleryPartner,
-							   g_system->getMillis(), macCD, macCD);
+							   macTalkie ? kPdaNotebookPartner : kPdaGalleryPartner,
+							   g_system->getMillis(), macTalkie, macTalkie, isLondon());
 				copyToScreen(scratch);
 				if (_audio)
 					_audio->sayKDDigital(reactIdx, 5, _partner);
 			}
 		}
-		waitForInput(macCD ? 0xFFFFFFFFu : 60000);
-		if (macCD && _audio)
+		waitForInput(macTalkie ? 0xFFFFFFFFu : 60000);
+		if (macTalkie && _audio)
 			_audio->stopSpool();
 
 		_mystery._firstTry = false;
@@ -4990,7 +4996,7 @@ void EEMEngine::doAccuse() {
 		const uint mn = _mystery.number();
 		const bool macRestored = isMacCD() && ConfMan.getBool("restored_content") &&
 			(mn == 0 || _restoredContentDataLoaded);
-		if (!isMacCD()) {
+		if (!isMacTalkie()) {
 			if (mn < sizeof(_mysteriesSolved))
 				_mysteriesSolved[mn] = _mystery._firstTry ? 2 : 1;
 			advanceChainStageAfterSolve(mn);
@@ -5011,8 +5017,8 @@ void EEMEngine::doAccuse() {
 			scratch.simpleBlitFrom(frame.surface);
 		if (winScene < _sitesArchive.size() &&
 			_sitesArchive.loadEntry(winScene, scene)) {
-			const int sx = isMacCD() ? 106 : scaleX(0x42);
-			const int sy = isMacCD() ? 38 : scaleY(0x14);
+			const int sx = isMacTalkie() ? 106 : scaleX(0x42);
+			const int sy = isMacTalkie() ? 38 : scaleY(0x14);
 			const int sw = MIN<int>(scene.surface.w, screenW - sx);
 			const int sh = MIN<int>(scene.surface.h, screenH - sy);
 			if (sw > 0 && sh > 0)
@@ -5020,22 +5026,25 @@ void EEMEngine::doAccuse() {
 										  scene.surface.pitch, sx, sy,
 										  sw, sh);
 		}
-		if (isMacCD()) {
+		if (isMacTalkie()) {
 			remapMacSurfaceEndpoints(scratch, getMacSpritePaletteMap());
 			setPartnerEraseBg(&scratch);
-			setPartnerIdleAnim(true, _partner == kPartnerJake ? 0x02 : 0x10, 7, 152);
+			setPartnerIdleAnim(true, _partner == kPartnerJake ? 0x02 : 0x10,
+				7, isLondon() && _partner == kPartnerJake ? 150 : 152);
 		}
 		blitPdaPartner(scratch, _aniArchive, _partner,
-					   isMacCD() ? kPdaNotebookPartner : kPdaGalleryPartner,
-					   g_system->getMillis(), isMacintosh(), isMacCD());
+					   isMacTalkie() ? kPdaNotebookPartner : kPdaGalleryPartner,
+					   g_system->getMillis(), isMacintosh(), isMacTalkie(), isLondon());
 		g_system->copyRectToScreen(scratch.getPixels(), scratch.pitch,
 								   0, 0, screenW, screenH);
 		g_system->updateScreen();
 
-		if (_music && (isMacCD() ? _musicOn : _voiceOn))
-			_music->playMus(5, /* loop= */ false);
-		if (isMacCD())
+		if (_music && (isMacTalkie() ? _musicOn : _voiceOn))
+			_music->playMus(isMacintosh() && isLondon() ? 31 : 5, false);
+		if (isMacTalkie())
 			waitForMusicDone();
+		if (isMacintosh() && isLondon() && _music && _musicOn && !shouldQuit())
+			_music->playMus(33, true);
 
 		const byte *solved = _mystery.solvedClueBlock();
 		if (isMacintosh() && _mystery.usesCompactMacData() && solved) {
@@ -5067,7 +5076,7 @@ void EEMEngine::doAccuse() {
 			// The practice CD script already includes the three scrapbook lines.
 			displayClue(solved, macRestored && mn == 0 && count > 3 ? count - 3 : count);
 		}
-		if (isMacCD()) {
+		if (isMacTalkie()) {
 			setPartnerIdleAnim(false, 0, 0, 0);
 			setPartnerEraseBg(nullptr);
 			if (shouldQuit())
@@ -5076,8 +5085,7 @@ void EEMEngine::doAccuse() {
 				_mysteriesSolved[mn] = _mystery._firstTry ? 2 : 1;
 			advanceChainStageAfterSolve(mn);
 		}
-		if (_music && _voiceOn)
-			_music->stop();
+		stopMusic();
 
 		if (!isMacintosh()) {
 			playAnm(Common::Path(isLondon() ? "SCRAP.ANM" : "SCRAPBK.ANI"),
@@ -5090,10 +5098,17 @@ void EEMEngine::doAccuse() {
 				displayScrapbookExtra(mn);
 				fadeCurrentPaletteToBlack();
 			}
+		} else if (isMacintosh() && isLondon()) {
+			fadeCurrentPaletteToBlack();
+			showStillPicture(0xa1, 0x43, 4000);
+			if (_music && _musicOn && !shouldQuit())
+				_music->playMus(93, true);
 		}
 
 		if (!shouldQuit())
 			doShowEnding(mn);
+		if (isMacintosh() && isLondon())
+			stopMusic();
 
 		_mystery.clear();
 		const Common::Error err = saveProfile(_playerName);
@@ -5778,7 +5793,7 @@ void EEMEngine::drawAccuseGallery(uint8 numSuspects, const byte *gd,
 	// Partner drawn first; defensive (no slot overlap).
 	blitPdaPartner(scratch, _aniArchive, _partner,
 				   isMacCD() ? kPdaNotebookPartner : kPdaGalleryPartner,
-				   g_system->getMillis(), mac, isMacCD());
+				   g_system->getMillis(), mac, isMacCD(), isLondon());
 	const GallerySlot * const slots = mac ? kMacGallerySlots : kGallerySlots;
 
 	for (uint i = 0; i < numSuspects && i < Mystery::kGalleryCap; i++) {
@@ -5806,7 +5821,7 @@ void EEMEngine::drawAccuseGallery(uint8 numSuspects, const byte *gd,
 			continue;
 
 		const int placeX = s.x;
-		const int placeY = isMacCD() ? s.y + 138 - portrait.surface.h :
+		const int placeY = isMacTalkie() ? s.y + 138 - portrait.surface.h :
 			(mac ? s.y : s.y + (0x48 - portrait.surface.h));
 		const int w = MIN<int>(portrait.surface.w, sw - placeX);
 		const int h = MIN<int>(portrait.surface.h, sh - placeY);
@@ -5825,7 +5840,7 @@ void EEMEngine::drawAccuseGallery(uint8 numSuspects, const byte *gd,
 	}
 
 	// Highlight outline (original uses `_PutMouseInRect` @ 1df2:0b8e).
-	if (!isMacCD() && highlighted >= 0 && highlighted < (int)slotRects.size() &&
+	if (!isMacTalkie() && highlighted >= 0 && highlighted < (int)slotRects.size() &&
 		!slotRects[highlighted].isEmpty()) {
 		Common::Rect r = slotRects[highlighted];
 		r.grow(1);


Commit: 43ef42c76405d5854b0d77bb77d3df5d8ae7286b
    https://github.com/scummvm/scummvm/commit/43ef42c76405d5854b0d77bb77d3df5d8ae7286b
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-09-11T08:11:32+02:00

Commit Message:
SCUMM: RA1: correctly render special german character in subtitles

Changed paths:
    engines/scumm/insane/rebel1/render.cpp


diff --git a/engines/scumm/insane/rebel1/render.cpp b/engines/scumm/insane/rebel1/render.cpp
index d10ba45825e..d5acbc79fc2 100644
--- a/engines/scumm/insane/rebel1/render.cpp
+++ b/engines/scumm/insane/rebel1/render.cpp
@@ -75,6 +75,11 @@ int ra1GameplayWindowOffsetY(const InsaneRebel1 *rebel1) {
 	}
 }
 
+int getBankGlyphIndex(byte ch) {
+	// Localized RA1 fonts store the DOS sharp-s glyph at 0x7F.
+	return (ch == 0xE1 ? 0x7F : ch) - 0x21;
+}
+
 void drawBankString(const RA1SpriteBank &bank, byte *dst, int pitch, int width, int height,
 	int x, int y, const char *text) {
 	if (!dst || !text || bank.numSprites <= 0)
@@ -93,7 +98,7 @@ void drawBankString(const RA1SpriteBank &bank, byte *dst, int pitch, int width,
 			x += 4;
 			continue;
 		}
-		const int fontIdx = (int)ch - 0x21;
+		const int fontIdx = getBankGlyphIndex(ch);
 		if (fontIdx < 0 || fontIdx >= bank.numSprites) {
 			x += 4;
 			continue;
@@ -144,7 +149,7 @@ const RA1Sprite *lookupBankGlyph(const RA1SpriteBank &bank, char ch) {
 	if ((byte)ch < 0x21)
 		return nullptr;
 
-	const int fontIdx = (int)(byte)ch - 0x21;
+	const int fontIdx = getBankGlyphIndex((byte)ch);
 	if (fontIdx < 0 || fontIdx >= bank.numSprites)
 		return nullptr;
 
@@ -186,7 +191,7 @@ int getBankStringWidth(const RA1SpriteBank &bank, const char *text) {
 			w += 4;
 			continue;
 		}
-		const int fontIdx = (int)ch - 0x21;
+		const int fontIdx = getBankGlyphIndex(ch);
 		if (fontIdx < 0 || fontIdx >= bank.numSprites) {
 			w += 4;
 			continue;




More information about the Scummvm-git-logs mailing list