[Scummvm-git-logs] scummvm master -> e2d16ffcecbf7e7d6fb25a6156ca1155a7605355

neuromancer noreply at scummvm.org
Sun Mar 29 13:28:28 UTC 2026


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

Summary:
3ef9c65c75 FREESCAPE: make the endgame for driller more stable
476425a3d3 FREESCAPE: avoid heartbeat to beat too fast
b300be71a3 FREESCAPE: loading of cpc images directly from data in dark cpc
167355fb19 FREESCAPE: wait before restoring ECD in dark
43ca847672 FREESCAPE: correctly render stipple patterns in CPC
e2d16ffcec FREESCAPE: better rendering from driller/castle cpc


Commit: 3ef9c65c7552c1a282c380f7c38048e4286b4b39
    https://github.com/scummvm/scummvm/commit/3ef9c65c7552c1a282c380f7c38048e4286b4b39
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-03-29T15:15:54+02:00

Commit Message:
FREESCAPE: make the endgame for driller more stable

Changed paths:
    engines/freescape/freescape.cpp
    engines/freescape/games/dark/dark.cpp
    engines/freescape/games/driller/driller.cpp
    engines/freescape/games/palettes.cpp
    engines/freescape/gfx.cpp
    engines/freescape/language/instruction.cpp
    engines/freescape/loaders/8bitBinaryLoader.cpp


diff --git a/engines/freescape/freescape.cpp b/engines/freescape/freescape.cpp
index 1788c584ed9..fec9c249c6c 100644
--- a/engines/freescape/freescape.cpp
+++ b/engines/freescape/freescape.cpp
@@ -458,7 +458,10 @@ void FreescapeEngine::checkSensors() {
 void FreescapeEngine::drawSensorShoot(Sensor *sensor) {}
 
 void FreescapeEngine::flashScreen(int backgroundColor) {
-	if (backgroundColor >= 16)
+	// CPC area colors are stored as 0..31 ink values, not 0..15 palette slots.
+	// Driller uses these raw values directly in the area headers and the original
+	// CPC code feeds them to the hardware/Gate Array without clamping.
+	if (backgroundColor >= (isCPC() ? 32 : 16))
 		return;
 	_currentArea->remapColor(_currentArea->_usualBackgroundColor, backgroundColor);
 	_currentArea->remapColor(_currentArea->_skyColor, backgroundColor);
diff --git a/engines/freescape/games/dark/dark.cpp b/engines/freescape/games/dark/dark.cpp
index 5360285998d..d44160db45b 100644
--- a/engines/freescape/games/dark/dark.cpp
+++ b/engines/freescape/games/dark/dark.cpp
@@ -710,8 +710,13 @@ void DarkEngine::gotoArea(uint16 areaID, int entranceID) {
 	_gfx->setColorRemaps(&_currentArea->_colorRemaps);
 
 	swapPalette(areaID);
-	_currentArea->_skyColor = isCPC() ? 1 : 0;
-	_currentArea->_usualBackgroundColor = isCPC() ? 1 : 0;
+	if (isCPC()) {
+		// The CPC loader still uses the generic area header parser, but the
+		// original Driller code does not use the first header byte as split
+		// sky/ground colors. Keep the real per-area background ink and reuse it
+		// for the viewport fill instead of interpreting the flag nibble as a sky.
+		_currentArea->_skyColor = _currentArea->_usualBackgroundColor;	
+	}
 
 	resetInput();
 }
diff --git a/engines/freescape/games/driller/driller.cpp b/engines/freescape/games/driller/driller.cpp
index 19b07cd85b1..b5c0b04c7dd 100644
--- a/engines/freescape/games/driller/driller.cpp
+++ b/engines/freescape/games/driller/driller.cpp
@@ -288,7 +288,7 @@ void DrillerEngine::gotoArea(uint16 areaID, int entranceID) {
 
 	debugC(1, kFreescapeDebugMove, "starting player position: %f, %f, %f", _position.x(), _position.y(), _position.z());
 	clearTemporalMessages();
-	// Ignore sky/ground fields
+	// DOS/Amiga/Atari ST ignore the area sky/background fields here.
 	_gfx->_keyColor = 0;
 	_gfx->setColorRemaps(&_currentArea->_colorRemaps);
 
@@ -298,8 +298,11 @@ void DrillerEngine::gotoArea(uint16 areaID, int entranceID) {
 		_currentArea->_skyColor = 0;
 		_currentArea->_usualBackgroundColor = 0;
 	} else if (isCPC()) {
-		_currentArea->_usualBackgroundColor = 1;
-		_currentArea->_skyColor = 1;
+		// The CPC loader still uses the generic area header parser, but the
+		// original Driller code does not use the first header byte as split
+		// sky/ground colors. Keep the real per-area background ink and reuse it
+		// for the viewport fill instead of interpreting the flag nibble as a sky.
+		_currentArea->_skyColor = _currentArea->_usualBackgroundColor;
 	}
 
 	resetInput();
@@ -915,19 +918,15 @@ bool DrillerEngine::checkIfGameEnded() {
 		if (_demoData[_demoIndex + 1] == 0x5f)
 			return true;
 
-	if ((isAmiga() || isAtariST()) && _gameStateControl == kFreescapeGameStatePlaying &&
-		_currentArea && _currentArea->getAreaID() == _endArea) {
-		stopMovement();
-		_endGameDelayTicks = 0;
-		_endGameKeyPressed = false;
-		_endGamePlayerEndArea = false;
-		_amigaAtariEndGameStep = -1;
+	if (_currentArea && _currentArea->getAreaID() == _endArea && 
+	    _gameStateControl == kFreescapeGameStatePlaying       &&
+		_gameStateVars[32] == 18
+	) { 
 		_gameStateControl = kFreescapeGameStateEnd;
 		return true;
 	}
 
-	FreescapeEngine::checkIfGameEnded();
-	return false;
+	return FreescapeEngine::checkIfGameEnded();
 }
 
 bool DrillerEngine::triggerWinCondition() {
@@ -941,14 +940,15 @@ bool DrillerEngine::triggerWinCondition() {
 }
 
 void DrillerEngine::endGame() {
-	if (isAmiga() || isAtariST()) {
-		_shootingFrames = 0;
-		_delayedShootObject = nullptr;
+	if (!_currentArea || _currentArea->getAreaID() != _endArea)
+		gotoArea(_endArea, _endEntrance);
+
+	_shootingFrames = 0;
+	_delayedShootObject = nullptr;
 
+	if (isAmiga() || isAtariST()) {
 		if (_amigaAtariEndGameStep < 0) {
 			stopMovement();
-			if (!_currentArea || _currentArea->getAreaID() != _endArea)
-				gotoArea(_endArea, _endEntrance);
 
 			// ENDGAME on Amiga/Atari ST switches to set 127 and then runs a 21-step
 			// flythrough. The original Amiga coordinates are stored at twice the engine
@@ -963,33 +963,19 @@ void DrillerEngine::endGame() {
 			_endGamePlayerEndArea = false;
 			_amigaAtariEndGameStep = 0;
 
-			for (int step = 0; step < 21; step++) {
+			for (int step = 0; step < 20; step++) {
 				_position.z() += 400.0f / areaScale;
 				_position.y() -= 140.0f / areaScale;
 				_lastPosition = _position;
 				waitInLoop(5);
 			}
-
-			waitInLoop(0); // Final BT01 before LIFTUP
 			waitInLoop(102);
-			_endGamePlayerEndArea = true;
-			waitInLoop(500);
-			_gameStateControl = kFreescapeGameStateRestart;
 		}
-		_endGameKeyPressed = false;
-		return;
 	} else {
-		FreescapeEngine::endGame();
-
-		if (!_endGamePlayerEndArea)
-			return;
-	}
-
-	if (_endGameKeyPressed) {
-		_gameStateControl = kFreescapeGameStateRestart;
+		waitInLoop(100);
 	}
 
-	_endGameKeyPressed = false;
+	_gameStateControl = kFreescapeGameStateRestart;
 }
 
 bool DrillerEngine::onScreenControls(Common::Point mouse) {
diff --git a/engines/freescape/games/palettes.cpp b/engines/freescape/games/palettes.cpp
index 7786d96fd0a..a5c9ee732d2 100644
--- a/engines/freescape/games/palettes.cpp
+++ b/engines/freescape/games/palettes.cpp
@@ -107,38 +107,38 @@ byte kDrillerZXPalette[9][3] = {
 };
 
 byte kDrillerCPCPalette[32][3] = {
-	{0x80, 0x80, 0x80}, // 0: special case?
-	{0x00, 0x00, 0x00}, // 1: used in dark only?
-	{0x00, 0x80, 0xff}, // 2
-	{0xff, 0xff, 0x80}, // 3
-	{0x00, 0x00, 0x80}, // 4
-	{0xff, 0x00, 0x80}, // 5
-	{0x00, 0x80, 0x80}, // 6
-	{0xff, 0x80, 0x80}, // 7
-	{0x80, 0x00, 0xff}, // 8
-	{0x00, 0x80, 0x00}, // 9
-	{0xff, 0xff, 0x00}, // 10
-	{0xff, 0xff, 0xff}, // 11
-	{0xff, 0x00, 0x00}, // 12
-	{0x11, 0x22, 0x33},
-	{0xff, 0x80, 0x00}, // 14
-	{0xff, 0x80, 0xff}, // 15
-	{0x11, 0x22, 0x33},
-	{0x00, 0xff, 0x80}, // 17
-	{0x00, 0xff, 0x00}, // 18
-	{0x80, 0xff, 0xff}, // 19
-	{0x80, 0x80, 0x80}, // 20
-	{0x00, 0x00, 0xff}, // 21
-	{0x00, 0x80, 0x00}, // 22
-	{0x00, 0x80, 0xff}, // 23
-	{0x80, 0x00, 0x80}, // 24
-	{0x80, 0xff, 0x80}, // 25
-	{0x80, 0xff, 0x00}, // 26
-	{0x00, 0xff, 0xff}, // 27
-	{0x80, 0x00, 0x00}, // 28
-	{0x80, 0x00, 0xff}, // 29
-	{0x80, 0x80, 0x00}, // 30
-	{0x80, 0x80, 0xff}, // 31
+	{0x80, 0x80, 0x80}, // 0: white
+	{0x80, 0x80, 0x80}, // 1: white
+	{0x00, 0xff, 0x80}, // 2: sea green
+	{0xff, 0xff, 0x80}, // 3: pastel yellow
+	{0x00, 0x00, 0x80}, // 4: blue
+	{0xff, 0x00, 0x80}, // 5: purple
+	{0x00, 0x80, 0x80}, // 6: cyan
+	{0xff, 0x80, 0x80}, // 7: pink
+	{0xff, 0x00, 0x80}, // 8: purple
+	{0xff, 0xff, 0x80}, // 9: pastel yellow
+	{0xff, 0xff, 0x00}, // 10: bright yellow
+	{0xff, 0xff, 0xff}, // 11: bright white
+	{0xff, 0x00, 0x00}, // 12: bright red
+	{0xff, 0x00, 0xff}, // 13: bright magenta
+	{0xff, 0x80, 0x00}, // 14: orange
+	{0xff, 0x80, 0xff}, // 15: pastel magenta
+	{0x00, 0x00, 0x80}, // 16: blue
+	{0x00, 0xff, 0x80}, // 17: sea green
+	{0x00, 0xff, 0x00}, // 18: bright green
+	{0x00, 0xff, 0xff}, // 19: bright cyan
+	{0x00, 0x00, 0x00}, // 20: black
+	{0x00, 0x00, 0xff}, // 21: bright blue
+	{0x00, 0x80, 0x00}, // 22: green
+	{0x00, 0x80, 0xff}, // 23: sky blue
+	{0x80, 0x00, 0x80}, // 24: magenta
+	{0x80, 0xff, 0x80}, // 25: pastel green
+	{0x80, 0xff, 0x00}, // 26: lime
+	{0x80, 0xff, 0xff}, // 27: pastel cyan
+	{0x80, 0x00, 0x00}, // 28: red
+	{0x80, 0x00, 0xff}, // 29: mauve
+	{0x80, 0x80, 0x00}, // 30: yellow
+	{0x80, 0x80, 0xff}, // 31: pastel blue
 };
 
 void FreescapeEngine::loadColorPalette() {
diff --git a/engines/freescape/gfx.cpp b/engines/freescape/gfx.cpp
index 0c1df92916c..329403f668a 100644
--- a/engines/freescape/gfx.cpp
+++ b/engines/freescape/gfx.cpp
@@ -494,6 +494,18 @@ bool Renderer::getRGBAtCPC(uint8 index, uint8 &r1, uint8 &g1, uint8 &b1, uint8 &
 		return true;
 	}
 	assert(_renderMode == Common::kRenderCPC);
+
+	// Driller CPC area/background colors are raw 0..31 ink values. Only
+	// Freescape drawable colors 1..15 use the packed four-pen color map.
+	if (index >= _colorMap->size() + 1) {
+		readFromPalette(index, r1, g1, b1);
+		r2 = r1;
+		g2 = g1;
+		b2 = b1;
+		stipple = nullptr;
+		return true;
+	}
+
 	stipple = (byte *)_stipples[index - 1];
 	byte *entry = (*_colorMap)[index - 1];
 	uint8 i1 = getCPCPixel(entry[0], 0, true);
diff --git a/engines/freescape/language/instruction.cpp b/engines/freescape/language/instruction.cpp
index c67acf8abda..6b2763dc616 100644
--- a/engines/freescape/language/instruction.cpp
+++ b/engines/freescape/language/instruction.cpp
@@ -355,8 +355,12 @@ void FreescapeEngine::executeRedraw(FCLInstruction &instruction) {
 	if (isEclipse2() && _currentArea->getAreaID() == _startArea && _gameStateControl == kFreescapeGameStateStart)
 		delay = delay * 10;
 
-	if (isCastle() && (isSpectrum() || isCPC()) && getGameBit(31))
+	if (isCastle() && (isSpectrum() || isCPC() || isC64()) && getGameBit(31))
 		delay = delay * 15; // Slow down redraws when the final cutscene is playing
+
+	if (isDriller() && (isSpectrum() || isCPC() || isC64()) && _gameStateVars[32] == 18)
+		delay = delay * 15; // Slow down redraws when the final cutscene is playing
+
 	waitInLoop(delay);
 }
 
diff --git a/engines/freescape/loaders/8bitBinaryLoader.cpp b/engines/freescape/loaders/8bitBinaryLoader.cpp
index b033012f6d3..1be36892b96 100644
--- a/engines/freescape/loaders/8bitBinaryLoader.cpp
+++ b/engines/freescape/loaders/8bitBinaryLoader.cpp
@@ -652,6 +652,9 @@ Area *FreescapeEngine::load8bitArea(Common::SeekableReadStream *file, uint16 nco
 	uint8 inkColor = 0;
 
 	if (!(isCastle() && (isSpectrum() || isCPC() || isC64()))) {
+		// On Driller/Dark/Eclipse 8-bit targets these are four consecutive
+		// per-area color bytes. CPC stores raw 0..31 ink values here, matching
+		// the original area descriptor layout at offsets +6..+9.
 		usualBackgroundColor = readField(file, 8);
 		underFireBackgroundColor = readField(file, 8);
 		paperColor = readField(file, 8);


Commit: 476425a3d3790eb0a8c769abe2332020da235dd6
    https://github.com/scummvm/scummvm/commit/476425a3d3790eb0a8c769abe2332020da235dd6
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-03-29T15:15:54+02:00

Commit Message:
FREESCAPE: avoid heartbeat to beat too fast

Changed paths:
    engines/freescape/freescape.cpp
    engines/freescape/games/eclipse/eclipse.cpp


diff --git a/engines/freescape/freescape.cpp b/engines/freescape/freescape.cpp
index fec9c249c6c..a682eff3f1c 100644
--- a/engines/freescape/freescape.cpp
+++ b/engines/freescape/freescape.cpp
@@ -937,7 +937,7 @@ void FreescapeEngine::endGame() {
 
 	_shootingFrames = 0;
 	_delayedShootObject = nullptr;
-	if (_gameStateControl == kFreescapeGameStateEnd && !isPlayingSound() && !_endGamePlayerEndArea) {
+	if (_gameStateControl == kFreescapeGameStateEnd && !_endGamePlayerEndArea) {
 		_endGamePlayerEndArea = true;
 		gotoArea(_endArea, _endEntrance);
 	}
diff --git a/engines/freescape/games/eclipse/eclipse.cpp b/engines/freescape/games/eclipse/eclipse.cpp
index 7a81e20f5dc..a9cb8f9f0d8 100644
--- a/engines/freescape/games/eclipse/eclipse.cpp
+++ b/engines/freescape/games/eclipse/eclipse.cpp
@@ -824,7 +824,7 @@ void EclipseEngine::drawHeartIndicator(Graphics::Surface *surface, int x, int y)
 	int beatStart = MAX(beatCycle - 5, 0);
 	int frame = _lastHeartIndicatorFrame;
 
-	if (_avoidRenderingFrames > 0 || _hasFallen) {
+	if (shield <= 5 || _avoidRenderingFrames > 0 || _hasFallen) {
 		frame = 1;
 		_lastHeartIndicatorFrame = frame;
 	} else if (!_inWaitLoop) {


Commit: b300be71a3707c4016891d2a8ce5ff59e1d4fd92
    https://github.com/scummvm/scummvm/commit/b300be71a3707c4016891d2a8ce5ff59e1d4fd92
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-03-29T15:15:54+02:00

Commit Message:
FREESCAPE: loading of cpc images directly from data in dark cpc

Changed paths:
    engines/freescape/games/dark/cpc.cpp
    engines/freescape/games/dark/dark.cpp
    engines/freescape/games/dark/dark.h
    engines/freescape/games/eclipse/eclipse.cpp


diff --git a/engines/freescape/games/dark/cpc.cpp b/engines/freescape/games/dark/cpc.cpp
index c29748f5178..9f67d7e1701 100644
--- a/engines/freescape/games/dark/cpc.cpp
+++ b/engines/freescape/games/dark/cpc.cpp
@@ -60,6 +60,60 @@ byte kCPCPaletteDarkTitle[16][3] = {
 	{0x00, 0x80, 0x00}, // 15: X
 };
 
+void DarkEngine::loadCPCIndicatorData(const byte *data, int widthBytes, int height, Common::Array<Graphics::ManagedSurface *> &target) {
+	Graphics::ManagedSurface *surface = new Graphics::ManagedSurface();
+	surface->create(widthBytes * 4, height, Graphics::PixelFormat::createFormatCLUT8());
+	surface->fillRect(Common::Rect(0, 0, surface->w, surface->h), 0);
+	Common::MemoryReadStream stream(data, widthBytes * height);
+	target.push_back(loadFrameCPCIndexed(&stream, surface, widthBytes, height));
+}
+
+void DarkEngine::loadCPCIndicator(Common::SeekableReadStream *file, uint32 offset, Common::Array<Graphics::ManagedSurface *> &target) {
+	// The HUD blitter at 0x7938 consumes records as { height, widthBytes, mode-1 row data... }.
+	file->seek(offset);
+	int height = file->readByte();
+	int widthBytes = file->readByte();
+	Common::Array<byte> data;
+	data.resize(widthBytes * height);
+	file->read(data.data(), data.size());
+	loadCPCIndicatorData(data.data(), widthBytes, height, target);
+}
+
+void DarkEngine::loadCPCIndicators(Common::SeekableReadStream *file) {
+	for (auto &indicator : _cpcIndicators) {
+		indicator->free();
+		delete indicator;
+	}
+	_cpcIndicators.clear();
+	for (auto &indicator : _cpcJetpackIndicators) {
+		indicator->free();
+		delete indicator;
+	}
+	_cpcJetpackIndicators.clear();
+	for (auto &indicator : _cpcActionIndicators) {
+		indicator->free();
+		delete indicator;
+	}
+	_cpcActionIndicators.clear();
+
+	loadCPCIndicator(file, 0x0F04, _cpcIndicators); // 0x6BFE -> 0x2AE6
+	loadCPCIndicator(file, 0x0E8A, _cpcIndicators); // 0x6C11 -> 0x2A6C
+	loadCPCIndicator(file, 0x0E10, _cpcIndicators); // 0x6C1B -> 0x29F2
+	loadCPCIndicator(file, 0x0D8F, _cpcIndicators); // 0x6C08 -> 0x2971
+
+	byte frame0[6];
+	byte frame1[6];
+	file->seek(0x0E09); // 0x52C6 -> 0x29EB
+	file->read(frame0, 6);
+	file->seek(0x0E0A); // 0x52CB -> 0x29EC
+	file->read(frame1, 6);
+	loadCPCIndicatorData(frame0, 1, 6, _cpcJetpackIndicators);
+	loadCPCIndicatorData(frame1, 1, 6, _cpcJetpackIndicators);
+
+	loadCPCIndicator(file, 0x0F7E, _cpcActionIndicators); // 0x507E -> 0x2B60
+	loadCPCIndicator(file, 0x0FB2, _cpcActionIndicators); // 0x508B -> 0x2B94
+}
+
 
 void DarkEngine::loadAssetsCPCFullGame() {
 	Common::File file;
@@ -90,13 +144,7 @@ void DarkEngine::loadAssetsCPCFullGame() {
 	loadGlobalObjects(&file, 0x9a, 23);
 	load8bitBinary(&file, 0x6255, 16);
 	loadSoundsCPC(&file, 0x09B7, 160, 0x0A57, 284, 0x0B73, 203);
-	_indicators.push_back(loadBundledImage("dark_fallen_indicator"));
-	_indicators.push_back(loadBundledImage("dark_crouch_indicator"));
-	_indicators.push_back(loadBundledImage("dark_walk_indicator"));
-	_indicators.push_back(loadBundledImage("dark_jet_indicator"));
-
-	for (auto &it : _indicators)
-		it->convertToInPlace(_gfx->_texturePixelFormat);
+	loadCPCIndicators(&file);
 }
 
 void DarkEngine::drawCPCUI(Graphics::Surface *surface) {
@@ -165,7 +213,7 @@ void DarkEngine::drawCPCUI(Graphics::Surface *surface) {
 		surface->fillRect(energyBar, front);
 	}
 	drawBinaryClock(surface, 300, 124, front, back);
-	drawIndicator(surface, 160, 136);
+	drawIndicator(surface, 160, 140);
 	drawVerticalCompass(surface, 24, 76, _pitch, front);
 }
 
diff --git a/engines/freescape/games/dark/dark.cpp b/engines/freescape/games/dark/dark.cpp
index d44160db45b..d3528306f7a 100644
--- a/engines/freescape/games/dark/dark.cpp
+++ b/engines/freescape/games/dark/dark.cpp
@@ -99,11 +99,25 @@ DarkEngine::DarkEngine(OSystem *syst, const ADGameDescription *gd) : FreescapeEn
 	_jetpackIndicatorTransitionFrame = 0;
 	_jetpackIndicatorTransitionDirection = 0;
 	_jetpackIndicatorNextFrameMillis = 0;
+	_cpcActionIndicatorUntilMillis = 0;
 }
 
 DarkEngine::~DarkEngine() {
 	delete _playerC64Sfx;
 	delete _playerC64Music;
+
+	for (auto &indicator : _cpcIndicators) {
+		indicator->free();
+		delete indicator;
+	}
+	for (auto &indicator : _cpcJetpackIndicators) {
+		indicator->free();
+		delete indicator;
+	}
+	for (auto &indicator : _cpcActionIndicators) {
+		indicator->free();
+		delete indicator;
+	}
 }
 
 void DarkEngine::addECDs(Area *area) {
@@ -722,6 +736,9 @@ void DarkEngine::gotoArea(uint16 areaID, int entranceID) {
 }
 
 void DarkEngine::pressedKey(const int keycode) {
+	if (isCPC())
+		_cpcActionIndicatorUntilMillis = g_system->getMillis() + 150;
+
 	// This code is duplicated in the DrillerEngine::pressedKey (except for the J case)
 	if (keycode == kActionIncreaseStepSize) {
 		increaseStepSize();
@@ -894,6 +911,11 @@ void DarkEngine::drawHorizontalCompass(int x, int y, float angle, uint32 front,
 	uint32 transparent = _gfx->_texturePixelFormat.ARGBToColor(0x00, 0x00, 0x00, 0x00);
 
 	uint32 green = _gfx->_texturePixelFormat.ARGBToColor(0xff, 0x00, 0xaa, 0x00);
+	if (isCPC()) {
+		uint8 r, g, b;
+		_gfx->selectColorFromFourColorPalette(3, r, g, b);
+		green = _gfx->_texturePixelFormat.ARGBToColor(0xFF, r, g, b);
+	}
 
 	int delta = (angle - 180) / 5.5;
 	Common::String compass = "-N-E-S-W-N-E-S";
@@ -913,7 +935,55 @@ void DarkEngine::drawHorizontalCompass(int x, int y, float angle, uint32 front,
 	surface->fillRect(Common::Rect(x + 80, y - 5, 320, y + 10), transparent);
 }
 
+void DarkEngine::drawCPCSprite(Graphics::Surface *surface, const Graphics::ManagedSurface *indicator, int xPosition, int yPosition) {
+	uint32 colors[4];
+	for (uint8 i = 0; i < 4; i++) {
+		uint8 r, g, b;
+		_gfx->selectColorFromFourColorPalette(i, r, g, b);
+		colors[i] = _gfx->_texturePixelFormat.ARGBToColor(0xFF, r, g, b);
+	}
+
+	for (int y = 0; y < indicator->h; y++) {
+		for (int x = 0; x < indicator->w; x++)
+			surface->setPixel(xPosition + x, yPosition + y, colors[(byte)indicator->getPixel(x, y)]);
+	}
+}
+
+void DarkEngine::drawCPCIndicator(Graphics::Surface *surface, int xPosition, int yPosition) {
+	if (_cpcIndicators.empty())
+		return;
+
+	const Graphics::ManagedSurface *indicator = nullptr;
+	if (_hasFallen)
+		indicator = _cpcIndicators[0];
+	else if (_flyMode)
+		indicator = _cpcIndicators[3];
+	else if (_playerHeightNumber == 0)
+		indicator = _cpcIndicators[1];
+	else
+		indicator = _cpcIndicators[2];
+
+	drawCPCSprite(surface, indicator, xPosition, yPosition);
+
+	if (_flyMode && _cpcJetpackIndicators.size() > 1) {
+		uint32 frame = (g_system->getMillis() / 40) & 1;
+		drawCPCSprite(surface, _cpcJetpackIndicators[frame], xPosition + 4, yPosition + 12);
+	}
+
+	bool showActionIndicator = _moveForward || _moveBackward || _moveUp || _moveDown ||
+		_shootingFrames > 0 || g_system->getMillis() < _cpcActionIndicatorUntilMillis;
+	if (showActionIndicator && _cpcActionIndicators.size() > 1) {
+		uint32 frame = (g_system->getMillis() / 40) & 1;
+		drawCPCSprite(surface, _cpcActionIndicators[frame], 256, 169);
+	}
+}
+
 void DarkEngine::drawIndicator(Graphics::Surface *surface, int xPosition, int yPosition) {
+	if (isCPC() && !_cpcIndicators.empty()) {
+		drawCPCIndicator(surface, xPosition, yPosition);
+		return;
+	}
+
 	if (_indicators.size() == 0)
 		return;
 	if (_hasFallen)
diff --git a/engines/freescape/games/dark/dark.h b/engines/freescape/games/dark/dark.h
index 6153cca0326..984bed34cf5 100644
--- a/engines/freescape/games/dark/dark.h
+++ b/engines/freescape/games/dark/dark.h
@@ -105,6 +105,10 @@ public:
 	Font _fontBig;
 	Font _fontMedium;
 	Font _fontSmall;
+	Common::Array<Graphics::ManagedSurface *> _cpcIndicators;
+	Common::Array<Graphics::ManagedSurface *> _cpcJetpackIndicators;
+	Common::Array<Graphics::ManagedSurface *> _cpcActionIndicators;
+	uint32 _cpcActionIndicatorUntilMillis;
 
 	// Dark Side Amiga stores the grounded jetpack indicator states as raw
 	// 4-plane bitplane data. The executable drives those frames through a tiny
@@ -158,6 +162,11 @@ private:
 	bool tryDestroyECD(int index);
 	bool tryDestroyECDFullGame(int index);
 	void addWalls(Area *area);
+	void loadCPCIndicator(Common::SeekableReadStream *file, uint32 offset, Common::Array<Graphics::ManagedSurface *> &target);
+	void loadCPCIndicatorData(const byte *data, int widthBytes, int height, Common::Array<Graphics::ManagedSurface *> &target);
+	void loadCPCIndicators(Common::SeekableReadStream *file);
+	void drawCPCSprite(Graphics::Surface *surface, const Graphics::ManagedSurface *indicator, int xPosition, int yPosition);
+	void drawCPCIndicator(Graphics::Surface *surface, int xPosition, int yPosition);
 	void drawVerticalCompass(Graphics::Surface *surface, int x, int y, float angle, uint32 color);
 	void drawHorizontalCompass(int x, int y, float angle, uint32 front, uint32 back, Graphics::Surface *surface);
 };
diff --git a/engines/freescape/games/eclipse/eclipse.cpp b/engines/freescape/games/eclipse/eclipse.cpp
index a9cb8f9f0d8..9512682f00a 100644
--- a/engines/freescape/games/eclipse/eclipse.cpp
+++ b/engines/freescape/games/eclipse/eclipse.cpp
@@ -363,7 +363,7 @@ void EclipseEngine::gotoArea(uint16 areaID, int entranceID) {
 
 	_gfx->_keyColor = 0;
 	swapPalette(areaID);
-	_currentArea->_usualBackgroundColor = isCPC() ? 1 : 0;
+	//_currentArea->_usualBackgroundColor = isCPC() ? 1 : 0;
 	if (isAmiga() || isAtariST())
 		_currentArea->_skyColor = 15;
 


Commit: 167355fb19e5761823ca60289fb07ab8042c3e93
    https://github.com/scummvm/scummvm/commit/167355fb19e5761823ca60289fb07ab8042c3e93
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-03-29T15:15:54+02:00

Commit Message:
FREESCAPE: wait before restoring ECD in dark

Changed paths:
    engines/freescape/games/dark/dark.cpp


diff --git a/engines/freescape/games/dark/dark.cpp b/engines/freescape/games/dark/dark.cpp
index d3528306f7a..c16c082cb89 100644
--- a/engines/freescape/games/dark/dark.cpp
+++ b/engines/freescape/games/dark/dark.cpp
@@ -553,6 +553,8 @@ bool DarkEngine::checkIfGameEnded() {
 		int index = _gameStateVars[kVariableDarkECD] - 1;
 		bool destroyed = tryDestroyECD(index);
 
+		waitInLoop(10);
+
 		if (destroyed) {
 			_gameStateVars[kVariableActiveECDs] -= 4;
 			_gameStateVars[k8bitVariableScore] += 52750;


Commit: 43ca8476727a462e24b54112bae25a60da93c3ac
    https://github.com/scummvm/scummvm/commit/43ca8476727a462e24b54112bae25a60da93c3ac
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-03-29T15:15:54+02:00

Commit Message:
FREESCAPE: correctly render stipple patterns in CPC

Changed paths:
    engines/freescape/gfx.cpp


diff --git a/engines/freescape/gfx.cpp b/engines/freescape/gfx.cpp
index 329403f668a..b7b53fdc1e7 100644
--- a/engines/freescape/gfx.cpp
+++ b/engines/freescape/gfx.cpp
@@ -507,9 +507,9 @@ bool Renderer::getRGBAtCPC(uint8 index, uint8 &r1, uint8 &g1, uint8 &b1, uint8 &
 	}
 
 	stipple = (byte *)_stipples[index - 1];
-	byte *entry = (*_colorMap)[index - 1];
-	uint8 i1 = getCPCPixel(entry[0], 0, true);
-	uint8 i2 = getCPCPixel(entry[0], 1, true);
+	byte pair = _colorPair[index - 1];
+	uint8 i1 = pair & 0xf;
+	uint8 i2 = (pair >> 4) & 0xf;
 	selectColorFromFourColorPalette(i1, r1, g1, b1);
 	selectColorFromFourColorPalette(i2, r2, g2, b2);
 	if (r1 == r2 && g1 == g2 && b1 == b2) {


Commit: e2d16ffcecbf7e7d6fb25a6156ca1155a7605355
    https://github.com/scummvm/scummvm/commit/e2d16ffcecbf7e7d6fb25a6156ca1155a7605355
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-03-29T15:15:54+02:00

Commit Message:
FREESCAPE: better rendering from driller/castle cpc

Changed paths:
    engines/freescape/freescape.cpp
    engines/freescape/freescape.h
    engines/freescape/games/castle/castle.cpp
    engines/freescape/games/castle/cpc.cpp
    engines/freescape/games/driller/cpc.cpp
    engines/freescape/games/eclipse/eclipse.cpp
    engines/freescape/games/palettes.cpp
    engines/freescape/gfx.cpp
    engines/freescape/loaders/8bitBinaryLoader.cpp


diff --git a/engines/freescape/freescape.cpp b/engines/freescape/freescape.cpp
index a682eff3f1c..3c2de11785e 100644
--- a/engines/freescape/freescape.cpp
+++ b/engines/freescape/freescape.cpp
@@ -39,6 +39,20 @@ namespace Freescape {
 
 FreescapeEngine *g_freescape;
 
+bool isEncodedCPCDirectColor(uint8 index) {
+	return index >= 64 && index < 96;
+}
+
+uint8 encodeCPCDirectColor(uint8 index) {
+	assert(index < 32);
+	return index + 64;
+}
+
+uint8 decodeCPCDirectColor(uint8 index) {
+	assert(isEncodedCPCDirectColor(index));
+	return index - 64;
+}
+
 byte getCPCPixelMode1(byte cpc_byte, int index) {
 	if (index == 0)
 		return ((cpc_byte & 0x08) >> 2) | ((cpc_byte & 0x80) >> 7);
@@ -458,11 +472,13 @@ void FreescapeEngine::checkSensors() {
 void FreescapeEngine::drawSensorShoot(Sensor *sensor) {}
 
 void FreescapeEngine::flashScreen(int backgroundColor) {
-	// CPC area colors are stored as 0..31 ink values, not 0..15 palette slots.
-	// Driller uses these raw values directly in the area headers and the original
-	// CPC code feeds them to the hardware/Gate Array without clamping.
-	if (backgroundColor >= (isCPC() ? 32 : 16))
+	if (isCPC()) {
+		if (backgroundColor >= 32 && !isEncodedCPCDirectColor(backgroundColor))
+			return;
+	} else if (backgroundColor >= 16) {
 		return;
+	}
+
 	_currentArea->remapColor(_currentArea->_usualBackgroundColor, backgroundColor);
 	_currentArea->remapColor(_currentArea->_skyColor, backgroundColor);
 	drawFrame();
diff --git a/engines/freescape/freescape.h b/engines/freescape/freescape.h
index 22c19b9ccab..3ea2f51b933 100644
--- a/engines/freescape/freescape.h
+++ b/engines/freescape/freescape.h
@@ -53,6 +53,10 @@ namespace Freescape {
 class Renderer;
 class Debugger;
 
+bool isEncodedCPCDirectColor(uint8 index);
+uint8 encodeCPCDirectColor(uint8 index);
+uint8 decodeCPCDirectColor(uint8 index);
+
 #define FREESCAPE_DATA_BUNDLE "freescape.dat"
 
 enum CameraMovement {
diff --git a/engines/freescape/games/castle/castle.cpp b/engines/freescape/games/castle/castle.cpp
index 008f0c5f32f..6cc0ba831c4 100644
--- a/engines/freescape/games/castle/castle.cpp
+++ b/engines/freescape/games/castle/castle.cpp
@@ -598,6 +598,7 @@ void CastleEngine::gotoArea(uint16 areaID, int entranceID) {
 	// Ignore sky/ground fields
 	_gfx->_keyColor = 0;
 	_gfx->clearColorPairArray();
+	_gfx->fillColorPairArray();
 
 	swapPalette(areaID);
 
@@ -637,14 +638,10 @@ void CastleEngine::gotoArea(uint16 areaID, int entranceID) {
 	if (isSpectrum())
 		_gfx->_paperColor = 0;
 
-	// Unclear why this is needed
 	if (isCPC()) {
-		for (int i = 0; i < 128; i++) {
-			_gfx->_stipples[2][i] = _gfx->_stipples[11][i];
-		}
-		ColorMap *cm = _gfx->_colorMap;
-		(*cm)[2] = (*cm)[11];
+		_currentArea->_skyColor = _currentArea->_usualBackgroundColor;
 	}
+
 	resetInput();
 
 	if (entranceID > 0) {
diff --git a/engines/freescape/games/castle/cpc.cpp b/engines/freescape/games/castle/cpc.cpp
index 4c92dc765ca..cc8dc6d0ead 100644
--- a/engines/freescape/games/castle/cpc.cpp
+++ b/engines/freescape/games/castle/cpc.cpp
@@ -489,9 +489,10 @@ void CastleEngine::drawCPCUI(Graphics::Surface *surface) {
 	_gfx->readFromPalette(color, r, g, b);
 	uint32 front = _gfx->_texturePixelFormat.ARGBToColor(0xFF, r, g, b);
 
-	color = 1;
-
-	_gfx->readFromPalette(color, r, g, b);
+	// Castle CPC draws the message strip text through the UI row-pointer path,
+	// and the original string routine erases with zero pixels there. In the CPC
+	// HUD palette that is pen 0, i.e. black, not global CPC palette index 0.
+	_gfx->selectColorFromFourColorPalette(0, r, g, b);
 	uint32 back = _gfx->_texturePixelFormat.ARGBToColor(0xFF, r, g, b);
 
 	Common::Rect backRect(97, 181, 232, 190);
diff --git a/engines/freescape/games/driller/cpc.cpp b/engines/freescape/games/driller/cpc.cpp
index a0cda34d34f..73a4436ebc2 100644
--- a/engines/freescape/games/driller/cpc.cpp
+++ b/engines/freescape/games/driller/cpc.cpp
@@ -164,6 +164,8 @@ void DrillerEngine::drawCPCUI(Graphics::Surface *surface) {
 	uint32 color = _currentArea->_underFireBackgroundColor;
 	uint8 r, g, b;
 
+	if (isEncodedCPCDirectColor(color))
+		color = decodeCPCDirectColor(color);
 	_gfx->readFromPalette(color, r, g, b);
 	uint32 front = _gfx->_texturePixelFormat.ARGBToColor(0xFF, r, g, b);
 
@@ -172,6 +174,8 @@ void DrillerEngine::drawCPCUI(Graphics::Surface *surface) {
 		color = (*_gfx->_colorRemaps)[color];
 	}
 
+	if (isEncodedCPCDirectColor(color))
+		color = decodeCPCDirectColor(color);
 	_gfx->readFromPalette(color, r, g, b);
 	uint32 back = _gfx->_texturePixelFormat.ARGBToColor(0xFF, r, g, b);
 
diff --git a/engines/freescape/games/eclipse/eclipse.cpp b/engines/freescape/games/eclipse/eclipse.cpp
index 9512682f00a..b984bffca00 100644
--- a/engines/freescape/games/eclipse/eclipse.cpp
+++ b/engines/freescape/games/eclipse/eclipse.cpp
@@ -363,7 +363,6 @@ void EclipseEngine::gotoArea(uint16 areaID, int entranceID) {
 
 	_gfx->_keyColor = 0;
 	swapPalette(areaID);
-	//_currentArea->_usualBackgroundColor = isCPC() ? 1 : 0;
 	if (isAmiga() || isAtariST())
 		_currentArea->_skyColor = 15;
 
diff --git a/engines/freescape/games/palettes.cpp b/engines/freescape/games/palettes.cpp
index a5c9ee732d2..933dea17616 100644
--- a/engines/freescape/games/palettes.cpp
+++ b/engines/freescape/games/palettes.cpp
@@ -232,6 +232,15 @@ void FreescapeEngine::swapPalette(uint16 levelID) {
 		_gfx->_paperColor = _areaMap[levelID]->_paperColor;
 		_gfx->_underFireBackgroundColor = _areaMap[levelID]->_underFireBackgroundColor;
 
+		if (isCPC()) {
+			if (isEncodedCPCDirectColor(_gfx->_inkColor))
+				_gfx->_inkColor = decodeCPCDirectColor(_gfx->_inkColor);
+			if (isEncodedCPCDirectColor(_gfx->_paperColor))
+				_gfx->_paperColor = decodeCPCDirectColor(_gfx->_paperColor);
+			if (isEncodedCPCDirectColor(_gfx->_underFireBackgroundColor))
+				_gfx->_underFireBackgroundColor = decodeCPCDirectColor(_gfx->_underFireBackgroundColor);
+		}
+
 		if (isC64()) {
 			_gfx->_inkColor %= 16;
 			_gfx->_paperColor %= 16;
diff --git a/engines/freescape/gfx.cpp b/engines/freescape/gfx.cpp
index b7b53fdc1e7..0d8742d134b 100644
--- a/engines/freescape/gfx.cpp
+++ b/engines/freescape/gfx.cpp
@@ -29,7 +29,7 @@
 	#include "graphics/opengl/context.h"
 #endif
 
-#include "freescape/gfx.h"
+#include "freescape/freescape.h"
 #include "freescape/objects/object.h"
 
 namespace Freescape {
@@ -260,7 +260,9 @@ void Renderer::setColorMap(ColorMap *colorMap_) {
 		}
 	} else if (_renderMode == Common::kRenderCPC) {
 		fillColorPairArray();
-		for (int i = 4; i < 15; i++) {
+		// Castle CPC uses color-map entry 3 as a genuine checker pattern,
+		// so CPC stipples need to be generated for all 15 Freescape entries.
+		for (int i = 0; i < 15; i++) {
 			byte pair = _colorPair[i];
 			byte c1 = pair & 0xf;
 			byte c2 = (pair >> 4) & 0xf;
@@ -382,6 +384,8 @@ bool Renderer::getRGBAtC64(uint8 index, uint8 &r1, uint8 &g1, uint8 &b1, uint8 &
 			stipple = nullptr;
 			return true;
 		}
+		if (isEncodedCPCDirectColor(index))
+			index = decodeCPCDirectColor(index);
 		readFromPalette(index, r1, g1, b1);
 		r2 = r1;
 		g2 = g1;
@@ -486,6 +490,8 @@ bool Renderer::getRGBAtCPC(uint8 index, uint8 &r1, uint8 &g1, uint8 &b1, uint8 &
 			stipple = nullptr;
 			return true;
 		}
+		if (isEncodedCPCDirectColor(index))
+			index = decodeCPCDirectColor(index);
 		readFromPalette(index, r1, g1, b1);
 		r2 = r1;
 		g2 = g1;
@@ -495,9 +501,8 @@ bool Renderer::getRGBAtCPC(uint8 index, uint8 &r1, uint8 &g1, uint8 &b1, uint8 &
 	}
 	assert(_renderMode == Common::kRenderCPC);
 
-	// Driller CPC area/background colors are raw 0..31 ink values. Only
-	// Freescape drawable colors 1..15 use the packed four-pen color map.
-	if (index >= _colorMap->size() + 1) {
+	if (isEncodedCPCDirectColor(index)) {
+		index = decodeCPCDirectColor(index);
 		readFromPalette(index, r1, g1, b1);
 		r2 = r1;
 		g2 = g1;
@@ -1238,6 +1243,8 @@ void Renderer::drawBackground(uint8 color) {
 
 	if (_colorRemaps && _colorRemaps->contains(color)) {
 		color = (*_colorRemaps)[color];
+		if (_renderMode == Common::kRenderCPC && isEncodedCPCDirectColor(color))
+			color = decodeCPCDirectColor(color);
 		readFromPalette(color, r1, g1, b1);
 		clear(r1, g1, b1);
 		return;
diff --git a/engines/freescape/loaders/8bitBinaryLoader.cpp b/engines/freescape/loaders/8bitBinaryLoader.cpp
index 1be36892b96..12b9b55f4c6 100644
--- a/engines/freescape/loaders/8bitBinaryLoader.cpp
+++ b/engines/freescape/loaders/8bitBinaryLoader.cpp
@@ -672,6 +672,17 @@ Area *FreescapeEngine::load8bitArea(Common::SeekableReadStream *file, uint16 nco
 		skyColor = 0;
 	}
 
+	if (isCPC() && isDriller()) {
+		// Driller CPC stores the four area colors at bytes +6..+9 as raw 0..31
+		// CPC ink values. The original code copies them straight into the live
+		// pens before programming the hardware, so encode them in the Area and
+		// let the generic renderer treat them as direct CPC inks.
+		usualBackgroundColor = encodeCPCDirectColor(usualBackgroundColor);
+		underFireBackgroundColor = encodeCPCDirectColor(underFireBackgroundColor);
+		paperColor = encodeCPCDirectColor(paperColor);
+		inkColor = encodeCPCDirectColor(inkColor);
+	}
+
 	debugC(1, kFreescapeDebugParser, "Colors usual background: %d", usualBackgroundColor);
 	debugC(1, kFreescapeDebugParser, "Colors under fire background: %d", underFireBackgroundColor);
 	debugC(1, kFreescapeDebugParser, "Color Paper: %d", paperColor);




More information about the Scummvm-git-logs mailing list