[Scummvm-git-logs] scummvm master -> 6793425f01a0acb000ea336f2a96ed9d433e8aba

alexbevi noreply at scummvm.org
Wed Oct 7 10:22:55 UTC 2026


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

Summary:
a0b6c9baaf HARVESTER: Preserve numbered dialogue responses
7e6bb821ee HARVESTER: Fix room exit when facing opposite direction
2f270f8577 HARVESTER: Preserve keyboard walk cycles after key release
258a1c2668 HARVESTER: Fix palette fade timing
107798fbad HARVESTER: Fix depth-scaled walking
2771b9beb7 HARVESTER: Preserve player walking through timer actions
4e05e6800f HARVESTER: Improve animation synchronization
4e6af2292f HARVESTER: Preserve player sprite bases across animation frames
78d34939f7 HARVESTER: Adjust target depth during sideways walking
05e5e68b42 HARVESTER: adjust player/npc animation frame logging
af5340ebc0 HARVESTER: adjust palette fade logging verbosity
6793425f01 HARVESTER: fix menu options overlap on game over


Commit: a0b6c9baafd7cc759b606ff3da5be66ee4b8d426
    https://github.com/scummvm/scummvm/commit/a0b6c9baafd7cc759b606ff3da5be66ee4b8d426
Author: Alex Bevilacqua (alex at alexbevi.com)
Date: 2026-10-07T06:22:18-04:00

Commit Message:
HARVESTER: Preserve numbered dialogue responses

Fixes #17292

Assisted-by: Codex:gpt-6.1-sol

Changed paths:
    engines/harvester/dialogue.cpp


diff --git a/engines/harvester/dialogue.cpp b/engines/harvester/dialogue.cpp
index 0ce30c30ea9..5267bbea0f5 100644
--- a/engines/harvester/dialogue.cpp
+++ b/engines/harvester/dialogue.cpp
@@ -457,6 +457,7 @@ public:
 	struct DialogueResponseOptionLayout {
 		Common::String text;
 		Common::Array<Common::String> wrappedLines;
+		char hotkey = 0;
 		int rowStart = 0;
 		int rowCount = 0;
 	};
@@ -842,7 +843,7 @@ public:
 					if (clickedTopic >= 0) {
 						selectedTopic = topics[(uint)clickedTopic];
 						selection.fromGenericBye = clickedTopic == (int)topics.size() - 1;
-						debugC(1, kDebugDialogue, "Harvester: keyword menu selected topic[%d]='%s'",
+						debugC(2, kDebugDialogue, "Harvester: keyword menu selected topic[%d]='%s' via click",
 							clickedTopic + 1, selectedTopic.c_str());
 						return Common::kNoError;
 					}
@@ -1158,6 +1159,9 @@ private:
 
 			DialogueResponseOptionLayout option;
 			option.text = optionText;
+			if (optionText.size() > 1 && optionText[0] >= '1' && optionText[0] <= '9' &&
+					optionText[1] == '.')
+				option.hotkey = optionText[0];
 			option.rowStart = (int)totalRows;
 			for (const Common::String &explicitLine : explicitLines) {
 				Common::Array<Common::String> wrappedExplicitLines;
@@ -1181,20 +1185,12 @@ private:
 			if (segmentText.empty())
 				return;
 
-			uint digitEnd = 0;
-			while (digitEnd < segmentText.size() && segmentText[digitEnd] >= '0' &&
-					segmentText[digitEnd] <= '9') {
-				++digitEnd;
-			}
-			const bool startsNewOption = digitEnd > 0 && digitEnd < segmentText.size() &&
-				segmentText[digitEnd] == '.';
+			const bool startsNewOption = segmentText.size() > 1 && segmentText[0] >= '1' &&
+				segmentText[0] <= '9' && segmentText[1] == '.';
 			if (startsNewOption) {
 				flushOption();
-
-				Common::String optionLine = segmentText.substr(digitEnd + 1);
-				optionLine.trim();
-				optionText = optionLine;
-				explicitLines.push_back(Common::move(optionLine));
+				optionText = segmentText;
+				explicitLines.push_back(Common::move(segmentText));
 				return;
 			}
 
@@ -1310,8 +1306,11 @@ private:
 		}
 
 		Common::Array<Common::String> optionTexts;
-		for (const DialogueResponseOptionLayout &option : options)
+		for (const DialogueResponseOptionLayout &option : options) {
 			optionTexts.push_back(option.text);
+			debugC(3, kDebugDialogue, "Harvester: response menu option[%u] hotkey=%c rowStart=%d rowCount=%d",
+				optionTexts.size(), option.hotkey ? option.hotkey : '-', option.rowStart, option.rowCount);
+		}
 		logDialogueMenuItems("Response menu", responseLineIndex, responseLine, optionTexts);
 
 		const IndexedBitmap *textboxBitmap = _art->getTextboxBitmap(resolveDialogueResponseTextboxIndex(totalRows));
@@ -1331,43 +1330,44 @@ private:
 					return result;
 
 				if (event.type == Common::EVENT_KEYDOWN) {
-					if (event.kbd.keycode == Common::KEYCODE_ESCAPE) {
-						if (responseLineIndex >= 0) {
-							debugC(1, kDebugDialogue, "Harvester: response menu line 0x%x (%d) cancelled",
-								responseLineIndex, responseLineIndex + 1);
-						} else {
-							debugC(1, kDebugDialogue, "Harvester: response menu text cancelled");
-						}
-						return Common::kNoError;
-					}
-					if (event.kbd.ascii >= '1' && event.kbd.ascii <= '9') {
-						const int menuIndex = event.kbd.ascii - '0';
-						if (menuIndex >= 1 && menuIndex <= (int)options.size()) {
-							selectedIndex = menuIndex;
+					debugC(3, kDebugDialogue, "Harvester: response menu keycode=%d ascii=%u",
+						(int)event.kbd.keycode, event.kbd.ascii);
+					if (event.kbd.keycode >= Common::KEYCODE_1 && event.kbd.keycode <= Common::KEYCODE_9) {
+						const char hotkey = '1' + event.kbd.keycode - Common::KEYCODE_1;
+						for (int optionIndex = (int)options.size() - 1; optionIndex >= 0; --optionIndex) {
+							const DialogueResponseOptionLayout &option = options[(uint)optionIndex];
+							if (option.hotkey != hotkey)
+								continue;
+							selectedIndex = optionIndex + 1;
 							if (responseLineIndex >= 0) {
-								debugC(1, kDebugDialogue,
-									"Harvester: response menu line 0x%x (%d) selected option[%d]='%s' via hotkey",
+								debugC(2, kDebugDialogue,
+									"Harvester: response menu line 0x%x (%d) selected option[%d]='%s' via hotkey=%c result=%d",
 									responseLineIndex, responseLineIndex + 1,
-									selectedIndex, options[(uint)(selectedIndex - 1)].text.c_str());
+									optionIndex + 1, option.text.c_str(), hotkey, selectedIndex);
 							} else {
-								debugC(1, kDebugDialogue,
-									"Harvester: response menu text selected option[%d]='%s' via hotkey",
-									selectedIndex, options[(uint)(selectedIndex - 1)].text.c_str());
+								debugC(2, kDebugDialogue,
+									"Harvester: response menu text selected option[%d]='%s' via hotkey=%c result=%d",
+									optionIndex + 1, option.text.c_str(), hotkey, selectedIndex);
 							}
 							return waitForPointerRelease();
 						}
 					}
-				} else if (event.type == Common::EVENT_LBUTTONDOWN && hoveredOptionIndex >= 0) {
-					selectedIndex = hoveredOptionIndex + 1;
+				} else if (event.type == Common::EVENT_LBUTTONDOWN) {
+					const int clickedOptionIndex = getResponseMenuItemAt(options, totalRows, event.mouse);
+					if (clickedOptionIndex < 0)
+						continue;
+					selectedIndex = clickedOptionIndex + 1;
 					if (responseLineIndex >= 0) {
-						debugC(1, kDebugDialogue,
-							"Harvester: response menu line 0x%x (%d) selected option[%d]='%s' via click",
+						debugC(2, kDebugDialogue,
+							"Harvester: response menu line 0x%x (%d) selected option[%d]='%s' via click x=%d y=%d result=%d",
 							responseLineIndex, responseLineIndex + 1,
-							selectedIndex, options[(uint)hoveredOptionIndex].text.c_str());
+							selectedIndex, options[(uint)clickedOptionIndex].text.c_str(),
+							event.mouse.x, event.mouse.y, selectedIndex);
 					} else {
-						debugC(1, kDebugDialogue,
-							"Harvester: response menu text selected option[%d]='%s' via click",
-							selectedIndex, options[(uint)hoveredOptionIndex].text.c_str());
+						debugC(2, kDebugDialogue,
+							"Harvester: response menu text selected option[%d]='%s' via click x=%d y=%d result=%d",
+							selectedIndex, options[(uint)clickedOptionIndex].text.c_str(),
+							event.mouse.x, event.mouse.y, selectedIndex);
 					}
 					return waitForPointerRelease();
 				}


Commit: 7e6bb821eebe3d3d98faf00af4a74bb7ae377f9a
    https://github.com/scummvm/scummvm/commit/7e6bb821eebe3d3d98faf00af4a74bb7ae377f9a
Author: Alex Bevilacqua (alex at alexbevi.com)
Date: 2026-10-07T06:22:18-04:00

Commit Message:
HARVESTER: Fix room exit when facing opposite direction

Assisted-by: Codex:gpt-6.1-sol

Changed paths:
    engines/harvester/player.cpp
    engines/harvester/player.h
    engines/harvester/room.cpp


diff --git a/engines/harvester/player.cpp b/engines/harvester/player.cpp
index 0c435957189..afe9bd37776 100644
--- a/engines/harvester/player.cpp
+++ b/engines/harvester/player.cpp
@@ -613,22 +613,22 @@ static void resetPlayerMoveTargetProgress(RoomPlayerState &playerState) {
 }
 
 static int resolveMoveTargetHorizontalDirection(const RoomSetupState &state,
-		const RoomPlayerState &playerState) {
+		const RoomPlayerState &playerState, int regionFacing) {
 	const float slack = Player::computeDepthScale(state, playerState.z) *
 		kRoomPlayerHorizontalTargetSlackBase;
 	const float frameLeftX = (float)computePlayerFrameLeftX(playerState);
-	if (frameLeftX < (float)playerState.targetX - slack)
+	if (frameLeftX < (float)playerState.targetX - slack || regionFacing == 2)
 		return 1;
-	if ((float)playerState.targetX + slack < frameLeftX)
+	if ((float)playerState.targetX + slack < frameLeftX || regionFacing == 1)
 		return -1;
 
 	return 0;
 }
 
-static int resolveMoveTargetDepthDirection(const RoomPlayerState &playerState) {
-	if (playerState.targetZ < playerState.z - kRoomPlayerDepthTargetSlack)
+static int resolveMoveTargetDepthDirection(const RoomPlayerState &playerState, int regionFacing) {
+	if (playerState.targetZ < playerState.z - kRoomPlayerDepthTargetSlack || regionFacing == 0)
 		return 1;
-	if (playerState.z + kRoomPlayerDepthTargetSlack < playerState.targetZ)
+	if (playerState.z + kRoomPlayerDepthTargetSlack < playerState.targetZ || regionFacing == 3)
 		return -1;
 
 	return 0;
@@ -639,13 +639,13 @@ static bool hasPlayerReachedMoveTarget(const RoomPlayerState &playerState) {
 }
 
 static void updatePlayerMoveTargetProgress(const RoomSetupState &state,
-		RoomPlayerState &playerState) {
+		RoomPlayerState &playerState, int regionFacing) {
 	if (!playerState.moveTargetXReached &&
-			resolveMoveTargetHorizontalDirection(state, playerState) == 0) {
+			resolveMoveTargetHorizontalDirection(state, playerState, regionFacing) == 0) {
 		playerState.moveTargetXReached = true;
 	}
 	if (!playerState.moveTargetZReached &&
-			resolveMoveTargetDepthDirection(playerState) == 0) {
+			resolveMoveTargetDepthDirection(playerState, regionFacing) == 0) {
 		playerState.moveTargetZReached = true;
 	}
 }
@@ -1633,12 +1633,16 @@ bool Player::startTurnAnimation(RoomPlayerState &playerState, int targetFacing)
 			playerState.facing < 0 || playerState.facing == targetFacing)
 		return false;
 
+	// update_actor_runtime_state (0x4d750) routes desired states 1/2 from the
+	// opposite horizontal facing through front-facing state 0.
+	const int nextFacing = (playerState.facing == 1 && targetFacing == 2) ||
+			(playerState.facing == 2 && targetFacing == 1) ? 0 : targetFacing;
 	PlayerTurnAnimationRange range;
-	if (!resolvePlayerTurnAnimationRange(playerState.facing, targetFacing, range))
+	if (!resolvePlayerTurnAnimationRange(playerState.facing, nextFacing, range))
 		return false;
 
 	playerState.turnActive = true;
-	playerState.turnTargetFacing = targetFacing;
+	playerState.turnTargetFacing = nextFacing;
 	playerState.turnFirstFrame = range.firstFrame;
 	playerState.turnLastFrame = range.lastFrame;
 	playerState.turnEndFrame = range.playBackwards ? range.firstFrame : range.lastFrame;
@@ -1649,9 +1653,9 @@ bool Player::startTurnAnimation(RoomPlayerState &playerState, int targetFacing)
 	playerState.entity->setAnimationRate(kRoomPlayerWalkAnimationRate);
 	playerState.entity->setCurrentFrame(range.playBackwards ? range.lastFrame : range.firstFrame);
 	debugC(1, kDebugPlayer,
-		"Harvester: player turn animation from=%d to=%d frames=%d..%d backwards=%d rate=%d",
-		playerState.facing, targetFacing, range.firstFrame, range.lastFrame,
-		range.playBackwards, kRoomPlayerWalkAnimationRate);
+		"Harvester: player turn animation from=%d to=%d frames=%d..%d backwards=%d rate=%d requested_facing=%d",
+		playerState.facing, nextFacing, range.firstFrame, range.lastFrame,
+		range.playBackwards, kRoomPlayerWalkAnimationRate, targetFacing);
 	return true;
 }
 
@@ -1678,13 +1682,16 @@ bool Player::updateTurnAnimationState(RoomPlayerState &playerState) {
 bool Player::stepMoveTarget(HarvesterEngine &engine, const RoomSetupState &state,
 		const Common::Array<ObjectRecord> &sceneObjects,
 		const Common::Array<AnimRecord> &sceneAnimations,
-		RoomPlayerState &playerState) {
+		RoomPlayerState &playerState, int regionFacing) {
 	if (!playerState.entity || !playerState.hasMoveTarget || playerState.turnActive || playerState.hitActive)
 		return false;
 
 	refreshPlayerCurrentBlocker(engine, sceneObjects, sceneAnimations, playerState);
 
-	updatePlayerMoveTargetProgress(state, playerState);
+	// run_harvester_main_loop (0x6dc70) latches the clicked region facing at
+	// 0xc3f0c. update_actor_runtime_state (0x4d750) uses it to keep the matching
+	// target component active inside the usual X/Z stopping tolerance.
+	updatePlayerMoveTargetProgress(state, playerState, regionFacing);
 	if (hasPlayerReachedMoveTarget(playerState)) {
 		playerState.hasMoveTarget = false;
 		return setIdleAnimation(playerState, playerState.facing >= 0 ? playerState.facing : 0);
@@ -1694,7 +1701,7 @@ bool Player::stepMoveTarget(HarvesterEngine &engine, const RoomSetupState &state
 	const int previousBottomY = playerState.bottomY;
 	int horizontalDirection = playerState.moveTargetXReached
 		? 0
-		: resolveMoveTargetHorizontalDirection(state, playerState);
+		: resolveMoveTargetHorizontalDirection(state, playerState, regionFacing);
 	if (horizontalDirection != 0 && isHorizontalDirectionBlockedByHistory(playerState, horizontalDirection)) {
 		if (engine.isPathfindingDebugEnabled()) {
 			debugC(1, kDebugPathfinding,
@@ -1708,7 +1715,7 @@ bool Player::stepMoveTarget(HarvesterEngine &engine, const RoomSetupState &state
 		horizontalDirection = 0;
 	}
 	int depthDirection = horizontalDirection == 0 && !playerState.moveTargetZReached
-		? resolveMoveTargetDepthDirection(playerState)
+		? resolveMoveTargetDepthDirection(playerState, regionFacing)
 		: 0;
 	if (depthDirection != 0 && isDepthDirectionBlockedByHistory(playerState, depthDirection)) {
 		if (engine.isPathfindingDebugEnabled()) {
@@ -1726,6 +1733,16 @@ bool Player::stepMoveTarget(HarvesterEngine &engine, const RoomSetupState &state
 		playerState.hasMoveTarget = false;
 		return setIdleAnimation(playerState, playerState.facing >= 0 ? playerState.facing : 0);
 	}
+	const int desiredFacing = resolveFacingFromMoveDirection(
+		horizontalDirection, depthDirection, playerState.facing);
+	if (regionFacing >= 0 && desiredFacing != playerState.facing &&
+			startTurnAnimation(playerState, desiredFacing)) {
+		debugC(2, kDebugPlayer,
+			"Harvester: player region target turn room='%s' required_facing=%d movement_facing=%d target=(%d,z=%.2f)",
+			state.roomName.c_str(), regionFacing, desiredFacing,
+			playerState.targetX, (double)playerState.targetZ);
+		return true;
+	}
 	if (!consumePlayerMovementTick(playerState))
 		return false;
 
@@ -1739,9 +1756,16 @@ bool Player::stepMoveTarget(HarvesterEngine &engine, const RoomSetupState &state
 	} else if (depthDirection != 0) {
 		const int verticalStep = computeRoomPlayerVerticalScreenStep();
 		const float depthStep = computeRoomPlayerDepthStep(state);
-		candidateBottomY = clampRoomMovementY(state,
-			playerState.bottomY + depthDirection * verticalStep);
-		candidateZ = stepTowardsFloat(playerState.z, playerState.targetZ, depthStep);
+		const bool depthBoundaryReached = depthDirection < 0
+			? playerState.z >= (float)state.roomMaxZ
+			: playerState.z <= (float)state.roomMinZ;
+		if (!depthBoundaryReached) {
+			candidateBottomY = clampRoomMovementY(state,
+				playerState.bottomY + depthDirection * verticalStep);
+			candidateZ = regionFacing == 0 || regionFacing == 3
+				? clampRoomDepth(state, playerState.z - depthDirection * depthStep)
+				: stepTowardsFloat(playerState.z, playerState.targetZ, depthStep);
+		}
 	}
 
 	if (engine.isPathfindingDebugEnabled()) {
@@ -1800,7 +1824,7 @@ bool Player::stepMoveTarget(HarvesterEngine &engine, const RoomSetupState &state
 			playerState.centerX, playerState.bottomY, (double)playerState.z,
 			actualFacing);
 	}
-	updatePlayerMoveTargetProgress(state, playerState);
+	updatePlayerMoveTargetProgress(state, playerState, regionFacing);
 	if (hasPlayerReachedMoveTarget(playerState)) {
 		playerState.hasMoveTarget = false;
 		(void)setIdleAnimation(playerState, actualFacing);
diff --git a/engines/harvester/player.h b/engines/harvester/player.h
index 867fdff5438..f3d0f102f0a 100644
--- a/engines/harvester/player.h
+++ b/engines/harvester/player.h
@@ -75,7 +75,7 @@ public:
 	static bool stepMoveTarget(HarvesterEngine &engine, const RoomSetupState &state,
 		const Common::Array<ObjectRecord> &sceneObjects,
 		const Common::Array<AnimRecord> &sceneAnimations,
-		RoomPlayerState &playerState);
+		RoomPlayerState &playerState, int regionFacing = -1);
 	static bool stepKeyboardMovement(HarvesterEngine &engine, const RoomSetupState &state,
 		const Common::Array<ObjectRecord> &sceneObjects,
 		const Common::Array<AnimRecord> &sceneAnimations,
diff --git a/engines/harvester/room.cpp b/engines/harvester/room.cpp
index cf99d81703c..554801617ee 100644
--- a/engines/harvester/room.cpp
+++ b/engines/harvester/room.cpp
@@ -3424,7 +3424,7 @@ Common::Error RoomSystem::runRoomLoop(Flow &flow, const Common::String &targetNa
 				playerRect.left, playerRect.top, playerRect.right, playerRect.bottom,
 				playerOverlapsRegion, region.actionTag.c_str());
 		}
-		if (playerOverlapsRegion)
+		if (playerOverlapsRegion && doesPlayerFacingMatchRegion(playerState.facing, region))
 			return;
 
 		Player::setRegionMoveTarget(scene.state, playerState,
@@ -4427,13 +4427,16 @@ Common::Error RoomSystem::runRoomLoop(Flow &flow, const Common::String &targetNa
 		if (playerCanAct &&
 				!playerState.attackActive && !playerState.hitActive &&
 				!keyboardAttackRequested && !idleState.active && !idleState.exiting) {
+			const RegionRecord *pendingRegion = findSceneRegionByName(scene.sceneRegions, pendingRegionName);
+			const int regionFacing = pendingRegion && pendingRegion->startEnabled
+				? pendingRegion->desiredFacing : -1;
 			if (Player::stepKeyboardMovement(_engine, scene.state, scene.sceneObjects, scene.sceneAnimations,
 					playerState, moveLeft, moveRight, moveUp, moveDown)) {
 				notePlayerActivity();
 				needsRedraw = true;
 			} else if (Player::stepMoveTarget(
 					_engine, scene.state, scene.sceneObjects, scene.sceneAnimations,
-					playerState)) {
+					playerState, regionFacing)) {
 				notePlayerActivity();
 				needsRedraw = true;
 			} else if (!moveLeft && !moveRight && !moveUp && !moveDown && !playerState.hasMoveTarget &&


Commit: 2f270f8577ee0a6213128f6769f16f38162b81fb
    https://github.com/scummvm/scummvm/commit/2f270f8577ee0a6213128f6769f16f38162b81fb
Author: Alex Bevilacqua (alex at alexbevi.com)
Date: 2026-10-07T06:22:18-04:00

Commit Message:
HARVESTER: Preserve keyboard walk cycles after key release

Assisted-by: Codex:gpt-6.1-sol

Changed paths:
    engines/harvester/player.cpp
    engines/harvester/room.cpp
    engines/harvester/room_support.h
    engines/harvester/runtime_entity.cpp


diff --git a/engines/harvester/player.cpp b/engines/harvester/player.cpp
index afe9bd37776..45723423e50 100644
--- a/engines/harvester/player.cpp
+++ b/engines/harvester/player.cpp
@@ -1380,6 +1380,7 @@ bool Player::setIdleAnimation(RoomPlayerState &playerState, int facing) {
 		playerState.entity->getAnimationRate() != 0;
 	playerState.facing = facing;
 	playerState.nextMovementTick = 0;
+	playerState.keyboardWalkRequest = -1;
 	playerState.entity->setAnimationRate(0);
 	playerState.entity->setAnimationFrameRange(range.idleFrame, range.idleFrame, false);
 	playerState.entity->setCurrentFrame(range.idleFrame);
@@ -1844,14 +1845,35 @@ bool Player::stepKeyboardMovement(HarvesterEngine &engine, const RoomSetupState
 		RoomPlayerState &playerState, bool moveLeft, bool moveRight, bool moveUp, bool moveDown) {
 	if (!playerState.entity || !supportsMovementBand(state))
 		return false;
-
-	const int horizontalInput = (moveRight ? 1 : 0) - (moveLeft ? 1 : 0);
-	const int verticalInput = (moveDown ? 1 : 0) - (moveUp ? 1 : 0);
-	if (horizontalInput == 0 && verticalInput == 0)
-		return false;
 	if (playerState.turnActive || playerState.hitActive)
 		return false;
 
+	int horizontalInput = (moveRight ? 1 : 0) - (moveLeft ? 1 : 0);
+	int verticalInput = (moveDown ? 1 : 0) - (moveUp ? 1 : 0);
+	const PlayerAnimationRange range = resolvePlayerAnimationRange(playerState.facing);
+	const int currentFrame = playerState.entity->getCurrentFrame();
+	const bool keyboardWalking = !playerState.hasMoveTarget &&
+		playerState.entity->getAnimationRate() == kRoomPlayerWalkAnimationRate &&
+		currentFrame >= range.walkFirstFrame && currentFrame <= range.walkLastFrame;
+	if (keyboardWalking) {
+		if (horizontalInput != 0 || verticalInput != 0)
+			playerState.keyboardWalkRequest = resolveFacingFromMoveDirection(
+				horizontalInput, verticalInput, playerState.facing);
+		if (!playerState.entity->didAnimationAdvanceLastTick())
+			return false;
+		const int stepFacing = currentFrame < range.walkLastFrame
+			? playerState.facing : playerState.keyboardWalkRequest;
+		horizontalInput = stepFacing == 1 ? -1 : stepFacing == 2 ? 1 : 0;
+		verticalInput = stepFacing == 3 ? -1 : stepFacing == 0 ? 1 : 0;
+	} else {
+		playerState.keyboardWalkRequest = -1;
+	}
+	if (horizontalInput == 0 && verticalInput == 0) {
+		if (playerState.hasMoveTarget || playerState.facing < 0)
+			return false;
+		return setIdleAnimation(playerState, playerState.facing);
+	}
+
 	const int previousCenterX = playerState.centerX;
 	const int previousBottomY = playerState.bottomY;
 	const float previousZ = playerState.z;
@@ -1892,13 +1914,15 @@ bool Player::stepKeyboardMovement(HarvesterEngine &engine, const RoomSetupState
 			}
 			return stopped;
 		}
-		return false;
+		return setIdleAnimation(playerState, playerState.facing >= 0 ? playerState.facing : 0);
 	}
 
 	const int desiredFacing = resolveFacingFromRoomMovement(
 		playerState.centerX, playerState.bottomY, candidateCenterX, candidateBottomY);
-	if (desiredFacing != playerState.facing && startTurnAnimation(playerState, desiredFacing))
+	if (desiredFacing != playerState.facing && startTurnAnimation(playerState, desiredFacing)) {
+		playerState.keyboardWalkRequest = -1;
 		return true;
+	}
 
 	if (!consumePlayerMovementTick(playerState))
 		return false;
@@ -1908,14 +1932,22 @@ bool Player::stepKeyboardMovement(HarvesterEngine &engine, const RoomSetupState
 		playerState, previousCenterX, previousBottomY, previousZ,
 		candidateCenterX, candidateBottomY, candidateZ);
 	if (!moved)
-		return false;
+		return setIdleAnimation(playerState, playerState.facing >= 0 ? playerState.facing : 0);
 
 	const int actualFacing = resolveFacingFromRoomMovement(
 		previousCenterX, previousBottomY, playerState.centerX, playerState.bottomY);
 	(void)setPlayerWalkAnimation(playerState, actualFacing);
+	if (keyboardWalking && currentFrame == range.walkLastFrame) {
+		playerState.keyboardWalkRequest = -1;
+		playerState.entity->setCurrentFrame(resolvePlayerAnimationRange(actualFacing).walkFirstFrame);
+		debugC(2, kDebugPlayer,
+			"Harvester: player keyboard walk bank restart facing=%d frame=%d",
+			actualFacing, playerState.entity->getCurrentFrame());
+	}
 	debugC(2, kDebugPlayer,
-		"Harvester: player keyboard move room='%s' input=(%d,%d) pos=(%d,%d,z=%.2f) facing=%d frame=%d moved=%d",
-		state.roomName.c_str(), horizontalInput, verticalInput,
+		"Harvester: player keyboard move room='%s' input=(%d,%d) step=(%d,%d) pos=(%d,%d,z=%.2f) facing=%d frame=%d moved=%d",
+		state.roomName.c_str(), (moveRight ? 1 : 0) - (moveLeft ? 1 : 0),
+		(moveDown ? 1 : 0) - (moveUp ? 1 : 0), horizontalInput, verticalInput,
 		playerState.centerX, playerState.bottomY, (double)playerState.z,
 		playerState.facing, playerState.entity->getCurrentFrame(), moved);
 	return true;
diff --git a/engines/harvester/room.cpp b/engines/harvester/room.cpp
index 554801617ee..ec3c206a63e 100644
--- a/engines/harvester/room.cpp
+++ b/engines/harvester/room.cpp
@@ -4439,11 +4439,6 @@ Common::Error RoomSystem::runRoomLoop(Flow &flow, const Common::String &targetNa
 					playerState, regionFacing)) {
 				notePlayerActivity();
 				needsRedraw = true;
-			} else if (!moveLeft && !moveRight && !moveUp && !moveDown && !playerState.hasMoveTarget &&
-					!playerState.turnActive && !playerState.hitActive &&
-					playerState.entity && playerState.facing >= 0 &&
-					Player::setIdleAnimation(playerState, playerState.facing)) {
-				needsRedraw = true;
 			}
 
 			if (!showingInspectText && !keyboardAttackRequested &&
diff --git a/engines/harvester/room_support.h b/engines/harvester/room_support.h
index 72793c1a92f..2c546f64153 100644
--- a/engines/harvester/room_support.h
+++ b/engines/harvester/room_support.h
@@ -61,6 +61,8 @@ struct RoomPlayerState {
 	// Mirrors native RenderEntityRuntime.player_combat_loadout_id (+0x11bc).
 	int combatLoadout = 0;
 	uint32 nextMovementTick = 0;
+	// Held input queues the next walk bank; release preserves that request.
+	int keyboardWalkRequest = -1;
 	bool hasMoveTarget = false;
 	int targetX = 0;
 	int targetBottomY = 0;
diff --git a/engines/harvester/runtime_entity.cpp b/engines/harvester/runtime_entity.cpp
index 5aec9b654e0..fc88a545ce2 100644
--- a/engines/harvester/runtime_entity.cpp
+++ b/engines/harvester/runtime_entity.cpp
@@ -446,6 +446,13 @@ bool Entity::tickVisualState(uint32 now) {
 	_nextAnimationTick = now + _animationTickInterval;
 	_animationAdvancedLastTick = true;
 
+	if (_classId == kRuntimeEntityClassPlayer) {
+		debugC(3, kDebugPlayer,
+			"Harvester: player animation advance frame=%d->%d range=%d..%d rate=%d tick=%u interval=%u next_tick=%u",
+			previousFrameIndex, _currentFrame, _firstFrame, _lastFrame,
+			_animationRate, now, _animationTickInterval, _nextAnimationTick);
+	}
+
 	if (_classId == kRuntimeEntityClassNpc) {
 		const AbmFrame &currentFrame = _frames[(uint)_currentFrame];
 		const Common::Point drawOrigin = getDrawOrigin();


Commit: 258a1c26687ce1cbc7181ba1fb980093dce1b5c7
    https://github.com/scummvm/scummvm/commit/258a1c26687ce1cbc7181ba1fb980093dce1b5c7
Author: Alex Bevilacqua (alex at alexbevi.com)
Date: 2026-10-07T06:22:18-04:00

Commit Message:
HARVESTER: Fix palette fade timing

Assisted-by: Codex:gpt-6.1-sol

Changed paths:
    engines/harvester/flow.cpp
    engines/harvester/flow.h
    engines/harvester/runtime_entity.cpp
    engines/harvester/runtime_entity.h


diff --git a/engines/harvester/flow.cpp b/engines/harvester/flow.cpp
index 00bf6597208..a7d0c987abd 100644
--- a/engines/harvester/flow.cpp
+++ b/engines/harvester/flow.cpp
@@ -80,8 +80,8 @@ static const int kIdentTextboxTextInsetX = 10;
 static const int kIdentTextboxTextInsetY = 5;
 static const int kNativeIdentTextLineSpacing = 3;
 static const char *const kPlayerActorEntityName = "PLAYER";
-static const uint32 kPaletteFadeTickMs = 4;
-static const float kPaletteFadeStep = 0.1f;
+// ramp_palette_brightness (0x23a30) waits four DOS centiseconds per step.
+static const uint32 kPaletteFadeIntervalTicks = 4;
 static const float kPaletteBrightnessBlack = 0.0f;
 static const float kPaletteBrightnessFull = 1.0f;
 static const uint32 kDemoEndingScreenDurationMs = 10000;
@@ -2403,19 +2403,16 @@ Common::Error Flow::beginRoomSetupTransition() {
 		}
 
 		if (hasVisiblePalette) {
-			for (float brightness = 1.0f - kPaletteFadeStep;
-					brightness > kPaletteBrightnessBlack; brightness -= kPaletteFadeStep) {
+			for (double brightness = 1.0;
+					brightness > kPaletteBrightnessBlack; brightness -= 0.1) {
+				const uint32 startTick = getRuntimeClockTicks();
 				setScaledDisplayPalette(*screen, displayPalette, brightness);
 				screen->makeAllDirty();
 				screen->update();
 
-				const uint32 nextTick = g_system->getMillis() + kPaletteFadeTickMs;
-				while ((int32)(nextTick - g_system->getMillis()) > 0) {
-					Common::Error result = Common::kNoError;
-					if (pumpTransitionEvents(result))
-						return result;
-					g_system->delayMillis(1);
-				}
+				Common::Error fadeError = Common::kNoError;
+				if (waitForPaletteFadeTick(startTick, fadeError))
+					return fadeError;
 			}
 
 			setScaledDisplayPalette(*screen, displayPalette, kPaletteBrightnessBlack);
@@ -2460,18 +2457,15 @@ Common::Error Flow::fadeInRoomScene(const byte *palette, float targetBrightness)
 	if (!screen || !palette)
 		return Common::kNoError;
 
-	for (float brightness = kPaletteFadeStep; brightness < targetBrightness; brightness += kPaletteFadeStep) {
+	for (double brightness = 0.0; brightness < targetBrightness; brightness += 0.1) {
+		const uint32 startTick = getRuntimeClockTicks();
 		setScaledPalette(*screen, palette, brightness);
 		screen->makeAllDirty();
 		screen->update();
 
-		const uint32 nextTick = g_system->getMillis() + kPaletteFadeTickMs;
-		while ((int32)(nextTick - g_system->getMillis()) > 0) {
-			Common::Error result = Common::kNoError;
-			if (pumpTransitionEvents(result))
-				return result;
-			g_system->delayMillis(1);
-		}
+		Common::Error fadeError = Common::kNoError;
+		if (waitForPaletteFadeTick(startTick, fadeError))
+			return fadeError;
 	}
 
 	setScaledPalette(*screen, palette, targetBrightness);
@@ -2486,21 +2480,18 @@ Common::Error Flow::fadePalette(const byte *palette, float fromBrightness,
 	if (!screen || !palette)
 		return Common::kNoError;
 
-	const float step = fromBrightness < toBrightness ? kPaletteFadeStep : -kPaletteFadeStep;
-	for (float brightness = fromBrightness;
+	const double step = fromBrightness < toBrightness ? 0.1 : -0.1;
+	for (double brightness = fromBrightness;
 			step > 0.0f ? brightness < toBrightness : brightness > toBrightness;
 			brightness += step) {
+		const uint32 startTick = getRuntimeClockTicks();
 		setScaledPalette(*screen, palette, brightness);
 		screen->makeAllDirty();
 		screen->update();
 
-		const uint32 nextTick = g_system->getMillis() + kPaletteFadeTickMs;
-		while ((int32)(nextTick - g_system->getMillis()) > 0) {
-			Common::Error eventError = Common::kNoError;
-			if (pumpTransitionEvents(eventError))
-				return eventError;
-			g_system->delayMillis(1);
-		}
+		Common::Error fadeError = Common::kNoError;
+		if (waitForPaletteFadeTick(startTick, fadeError))
+			return fadeError;
 	}
 
 	setScaledPalette(*screen, palette, toBrightness);
@@ -2509,6 +2500,19 @@ Common::Error Flow::fadePalette(const byte *palette, float fromBrightness,
 	return Common::kNoError;
 }
 
+bool Flow::waitForPaletteFadeTick(uint32 startTick, Common::Error &result) {
+	const uint32 deadline = startTick + kPaletteFadeIntervalTicks;
+	while ((int32)(deadline - getRuntimeClockTicks()) > 0) {
+		if (pumpTransitionEvents(result))
+			return true;
+		g_system->delayMillis(1);
+	}
+	debugC(3, kDebugScene,
+		"Harvester: palette fade step tick=%u->%u interval=%u",
+		startTick, getRuntimeClockTicks(), kPaletteFadeIntervalTicks);
+	return false;
+}
+
 bool Flow::pumpTransitionEvents(Common::Error &result) {
 	Common::Event event;
 	while (g_system->getEventManager()->pollEvent(event)) {
diff --git a/engines/harvester/flow.h b/engines/harvester/flow.h
index bbf255d7328..8c42144c327 100644
--- a/engines/harvester/flow.h
+++ b/engines/harvester/flow.h
@@ -83,6 +83,7 @@ private:
 	Common::Error waitForRoomSetupTransitionHold();
 	Common::Error fadeInRoomScene(const byte *palette, float targetBrightness);
 	Common::Error fadePalette(const byte *palette, float fromBrightness, float toBrightness);
+	bool waitForPaletteFadeTick(uint32 startTick, Common::Error &result);
 	bool pumpTransitionEvents(Common::Error &result);
 	void executeStartupAudioCommands(const Common::Array<AudioCommand> &commands);
 	void queueDialogueInteraction(const InteractionResult &interaction);
diff --git a/engines/harvester/runtime_entity.cpp b/engines/harvester/runtime_entity.cpp
index fc88a545ce2..c6291aacdf8 100644
--- a/engines/harvester/runtime_entity.cpp
+++ b/engines/harvester/runtime_entity.cpp
@@ -69,18 +69,6 @@ static void scaleIndexedBitmapNearest(const IndexedBitmap &source, IndexedBitmap
 	}
 }
 
-static uint32 getAnimationClockTicks() {
-	if (!g_system)
-		return 0;
-
-	// Native obtains time through DOS int 21h/AH=2Ch. Its hundredths field
-	// advances on the 18.2 Hz BIOS timer, so preserve that quantization instead
-	// of deriving idealized centiseconds directly from wall-clock milliseconds.
-	const uint64 pitTicks = ((uint64)g_system->getMillis() * kDosPitInputFrequency) /
-		((uint64)kDosPitTimerDivisor * 1000U);
-	return (uint32)((pitTicks * kDosPitTimerDivisor * 100U) / kDosPitInputFrequency);
-}
-
 static void blitAnimationFrame(Graphics::Screen &screen, const Common::Array<AbmFrame> &frames, uint frameIndex,
 		int x, int y) {
 	if (frameIndex >= frames.size() || !frames[frameIndex].isValid())
@@ -152,6 +140,18 @@ static bool decodeAnimationFrame(const byte *source, uint32 sourceSize, bool com
 
 } // End of anonymous namespace
 
+uint32 getRuntimeClockTicks() {
+	if (!g_system)
+		return 0;
+
+	// Original game obtains time through DOS int 21h/AH=2Ch. Its hundredths field
+	// advances on the 18.2 Hz BIOS timer, so preserve that quantization instead
+	// of deriving idealized centiseconds directly from wall-clock milliseconds.
+	const uint64 pitTicks = ((uint64)g_system->getMillis() * kDosPitInputFrequency) /
+		((uint64)kDosPitTimerDivisor * 1000U);
+	return (uint32)((pitTicks * kDosPitTimerDivisor * 100U) / kDosPitInputFrequency);
+}
+
 static bool isRectangleOnlyHitClass(int classId) {
 	return classId == kRuntimeEntityClassRectHotspot ||
 		classId == kRuntimeEntityClassRectHotspot18 ||
@@ -517,7 +517,7 @@ void Entity::setTimerEnabled(bool enabled) {
 		return;
 	}
 
-	const uint32 now = getAnimationClockTicks();
+	const uint32 now = getRuntimeClockTicks();
 	const int remainingValue = _timerCurrentValue > 0 ? _timerCurrentValue : _timerInitialValue;
 	_timerEnabled = true;
 	_timerPaused = false;
@@ -991,7 +991,7 @@ void EntityManager::configureSceneTimerEntity(Entity &entity, int initialValue,
 		bool enabled, bool looping, bool global) {
 	entity.configureTimerCountdown(initialValue, currentValue, enabled, looping, global);
 	if (_timerPauseDepth > 0)
-		entity.pauseTimerCountdown(getAnimationClockTicks());
+		entity.pauseTimerCountdown(getRuntimeClockTicks());
 }
 
 void EntityManager::hideCursor() {
@@ -1008,7 +1008,7 @@ void EntityManager::pauseTimerCountdowns() {
 	if (_timerPauseDepth++ > 0)
 		return;
 
-	const uint32 now = getAnimationClockTicks();
+	const uint32 now = getRuntimeClockTicks();
 	for (Entity *entity : _sceneEntities)
 		entity->pauseTimerCountdown(now);
 }
@@ -1019,7 +1019,7 @@ void EntityManager::resumeTimerCountdowns() {
 	if (--_timerPauseDepth > 0)
 		return;
 
-	const uint32 now = getAnimationClockTicks();
+	const uint32 now = getRuntimeClockTicks();
 	for (Entity *entity : _sceneEntities)
 		entity->resumeTimerCountdown(now);
 }
@@ -1034,7 +1034,7 @@ bool EntityManager::takeExpiredTimerNames(Common::Array<Common::String> &expired
 }
 
 bool EntityManager::tickSceneEntities() {
-	const uint32 now = getAnimationClockTicks();
+	const uint32 now = getRuntimeClockTicks();
 	bool changed = false;
 
 	for (Entity *entity : _sceneEntities) {
@@ -1056,7 +1056,7 @@ bool EntityManager::syncCursorEntityPosition(const Common::Point &position) {
 
 	const bool moved = _cursorEntity->getX() != position.x || _cursorEntity->getY() != position.y;
 	_cursorEntity->setPosition(position.x, position.y, kCursorEntityZ);
-	return _cursorEntity->tickVisualState(getAnimationClockTicks()) || moved;
+	return _cursorEntity->tickVisualState(getRuntimeClockTicks()) || moved;
 }
 
 void EntityManager::drawCursor(Graphics::Screen &screen) const {
diff --git a/engines/harvester/runtime_entity.h b/engines/harvester/runtime_entity.h
index 3b1f0a1125b..e8fa15adbc2 100644
--- a/engines/harvester/runtime_entity.h
+++ b/engines/harvester/runtime_entity.h
@@ -35,6 +35,8 @@ namespace Harvester {
 
 class ResourceManager;
 
+uint32 getRuntimeClockTicks();
+
 enum RuntimeEntityClass {
 	kRuntimeEntityClassObject = 0,
 	kRuntimeEntityClassAnimation = 1,


Commit: 107798fbad816045c3559a50cbe624f68bd4ee50
    https://github.com/scummvm/scummvm/commit/107798fbad816045c3559a50cbe624f68bd4ee50
Author: Alex Bevilacqua (alex at alexbevi.com)
Date: 2026-10-07T06:22:18-04:00

Commit Message:
HARVESTER: Fix depth-scaled walking

Related to #17297 (doesn't fix it entirely)

Assisted-by: Codex:gpt-6.1-sol

Changed paths:
    engines/harvester/player.cpp
    engines/harvester/runtime_entity.cpp
    engines/harvester/runtime_entity.h


diff --git a/engines/harvester/player.cpp b/engines/harvester/player.cpp
index 45723423e50..3f058e74409 100644
--- a/engines/harvester/player.cpp
+++ b/engines/harvester/player.cpp
@@ -55,7 +55,6 @@ static const float kRoomPlayerAttackUpperYOffset = 144.44f;
 static const float kRoomPlayerAttackMidYOffset = 75.36f;
 static const char *const kPlayerIdleAnimationEntityName = "IDLE_ANIM";
 static const char *const kPlayerIdleAnimationResourcePath = "1:/GRAPHIC/ROOMANIM/PCLOUN02.ABM";
-static const uint32 kRuntimeClockDivisorMs = 10;
 static const uint32 kRoomPlayerIdleDelayTicks = 3000;
 static const int kPlayerHitKnockbackDistance = 18;
 static const int kPlayerHitKnockbackDecayStep = 3;
@@ -1193,7 +1192,7 @@ bool Player::supportsMovementBand(const RoomSetupState &state) {
 }
 
 uint32 Player::getRuntimeClockTicks() {
-	return g_system ? (g_system->getMillis() / kRuntimeClockDivisorMs) : 0;
+	return Harvester::getRuntimeClockTicks();
 }
 
 bool Player::isIdleAnimationExcludedRoom(const Common::String &roomName) {
diff --git a/engines/harvester/runtime_entity.cpp b/engines/harvester/runtime_entity.cpp
index c6291aacdf8..b6607d08c1e 100644
--- a/engines/harvester/runtime_entity.cpp
+++ b/engines/harvester/runtime_entity.cpp
@@ -180,6 +180,8 @@ bool Entity::loadBitmapResource(ResourceManager &resources, const Common::String
 	_frames.clear();
 	_frames.push_back(Common::move(frame));
 	_baseFrames = _frames;
+	_frameDepthScales.clear();
+	_frameDepthScales.resize(_frames.size(), 1.0f);
 	_resourcePath = path;
 	_currentFrame = 0;
 	_firstFrame = 0;
@@ -233,6 +235,8 @@ bool Entity::loadAbmResource(ResourceManager &resources, const Common::String &p
 
 	_resourcePath = path;
 	_baseFrames = _frames;
+	_frameDepthScales.clear();
+	_frameDepthScales.resize(_frames.size(), 1.0f);
 	_currentFrame = _frames.empty() ? -1 : 0;
 	_firstFrame = _currentFrame;
 	_lastFrame = _frames.empty() ? -1 : (int)_frames.size() - 1;
@@ -369,6 +373,7 @@ void Entity::setAnimationSequence(int sequence) {
 void Entity::configureHotspotBounds(int width, int height) {
 	_frames.clear();
 	_baseFrames.clear();
+	_frameDepthScales.clear();
 	_currentFrame = -1;
 	_firstFrame = -1;
 	_lastFrame = -1;
@@ -424,11 +429,15 @@ void Entity::setDepthScale(float scale) {
 	if (fabsf(_depthScale - newScale) < 0.0001f)
 		return;
 
-	if (_baseFrames.empty())
+	if (_baseFrames.empty()) {
 		_baseFrames = _frames;
+		_frameDepthScales.clear();
+		_frameDepthScales.resize(_frames.size(), 1.0f);
+	}
 
 	_depthScale = newScale;
-	rebuildScaledFrames();
+	updateBoundsFromCurrentFrame();
+	updateScreenBaseFromCurrentFrame();
 }
 
 bool Entity::tickVisualState(uint32 now) {
@@ -764,6 +773,7 @@ done:
 }
 
 void Entity::updateBoundsFromCurrentFrame() {
+	scaleCurrentFrame();
 	if (_frames.empty()) {
 		_boundsWidth = 0;
 		_boundsHeight = 0;
@@ -788,36 +798,31 @@ void Entity::updateScreenBaseFromCurrentFrame() {
 	_screenBaseY = baseY;
 }
 
-void Entity::rebuildScaledFrames() {
-	if (_baseFrames.empty()) {
-		_frames.clear();
-		updateBoundsFromCurrentFrame();
-		updateScreenBaseFromCurrentFrame();
+void Entity::scaleCurrentFrame() {
+	if (_baseFrames.empty() || _currentFrame < 0 ||
+			(uint)_currentFrame >= _baseFrames.size())
 		return;
-	}
 
-	if (fabsf(_depthScale - 1.0f) < 0.0001f) {
-		_frames = _baseFrames;
-		updateBoundsFromCurrentFrame();
-		updateScreenBaseFromCurrentFrame();
+	const uint frameIndex = (uint)_currentFrame;
+	if (_frameDepthScales[frameIndex] == _depthScale)
 		return;
-	}
 
-	_frames.resize(_baseFrames.size());
-	for (uint i = 0; i < _baseFrames.size(); ++i) {
-		const AbmFrame &source = _baseFrames[i];
-		AbmFrame &scaled = _frames[i];
-		const int scaledWidth = scaleDimension(source.width, _depthScale);
-		const int scaledHeight = scaleDimension(source.height, _depthScale);
-
-		scaleIndexedBitmapNearest(source, scaled, scaledWidth, scaledHeight);
+	const AbmFrame &source = _baseFrames[frameIndex];
+	AbmFrame &scaled = _frames[frameIndex];
+	if (fabsf(_depthScale - 1.0f) < 0.0001f) {
+		scaled = source;
+	} else {
+		scaleIndexedBitmapNearest(source, scaled,
+			scaleDimension(source.width, _depthScale),
+			scaleDimension(source.height, _depthScale));
 		// Native depth scaling preserves the authored horizontal frame offset.
 		scaled.xOffset = source.xOffset;
 		scaled.yOffset = roundToInt((float)source.yOffset * _depthScale);
 	}
-
-	updateBoundsFromCurrentFrame();
-	updateScreenBaseFromCurrentFrame();
+	_frameDepthScales[frameIndex] = _depthScale;
+	debugC(3, kDebugPlayer,
+		"Harvester: entity depth scale name='%s' frame=%u scale=%.4f size=%ux%u",
+		_name.c_str(), frameIndex, (double)_depthScale, scaled.width, scaled.height);
 }
 
 EntityManager::EntityManager(ResourceManager &resources) : _resources(resources) {
diff --git a/engines/harvester/runtime_entity.h b/engines/harvester/runtime_entity.h
index e8fa15adbc2..61fdd0f0b39 100644
--- a/engines/harvester/runtime_entity.h
+++ b/engines/harvester/runtime_entity.h
@@ -136,12 +136,13 @@ private:
 	void advanceAnimationFrame(int directive);
 	void updateBoundsFromCurrentFrame();
 	void updateScreenBaseFromCurrentFrame();
-	void rebuildScaledFrames();
+	void scaleCurrentFrame();
 
 	Common::String _name;
 	Common::String _resourcePath;
 	Common::Array<AbmFrame> _frames;
 	Common::Array<AbmFrame> _baseFrames;
+	Common::Array<float> _frameDepthScales;
 	int _classId = 0;
 	int _x = 0;
 	int _y = 0;


Commit: 2771b9beb714d49fa4f621320ea7e176597e2aa8
    https://github.com/scummvm/scummvm/commit/2771b9beb714d49fa4f621320ea7e176597e2aa8
Author: Alex Bevilacqua (alex at alexbevi.com)
Date: 2026-10-07T06:22:18-04:00

Commit Message:
HARVESTER: Preserve player walking through timer actions

Assisted-by: Codex:gpt-6.1-sol

Changed paths:
    engines/harvester/room.cpp
    engines/harvester/room_interaction.cpp
    engines/harvester/room_interaction.h


diff --git a/engines/harvester/room.cpp b/engines/harvester/room.cpp
index ec3c206a63e..a4ed59b22cc 100644
--- a/engines/harvester/room.cpp
+++ b/engines/harvester/room.cpp
@@ -4489,7 +4489,7 @@ Common::Error RoomSystem::runRoomLoop(Flow &flow, const Common::String &targetNa
 					bool didTransition = false;
 					Common::Error interactionError =
 						interactionProcessor.handleInteractionResult(
-							timerInteraction, didTransition, Common::String());
+							timerInteraction, didTransition, Common::String(), false);
 					if (interactionError.getCode() != Common::kNoError)
 						return interactionError;
 					if (flow.hasPendingMainMenuReturn())
diff --git a/engines/harvester/room_interaction.cpp b/engines/harvester/room_interaction.cpp
index b41f6c9f52e..464759091bf 100644
--- a/engines/harvester/room_interaction.cpp
+++ b/engines/harvester/room_interaction.cpp
@@ -51,13 +51,21 @@ RoomInteractionProcessor::RoomInteractionProcessor(HarvesterEngine &engine, Flow
 }
 
 Common::Error RoomInteractionProcessor::handleInteractionResult(const InteractionResult &interaction,
-		bool &didTransition, const Common::String &usedItemName) {
+		bool &didTransition, const Common::String &usedItemName, bool interruptPlayerMovement) {
 	didTransition = false;
 
-	_playerState.hasMoveTarget = false;
-	_playerState.turnActive = false;
-	_playerState.turnTargetFacing = -1;
-	_pendingRegionName.clear();
+	if (interruptPlayerMovement) {
+		_playerState.hasMoveTarget = false;
+		_playerState.turnActive = false;
+		_playerState.turnTargetFacing = -1;
+		_pendingRegionName.clear();
+	} else if (_playerState.hasMoveTarget || _playerState.turnActive) {
+		debugC(2, kDebugPlayer,
+			"Harvester: background action preserving player movement room='%s' pos=(%d,%d,z=%.2f) target=(%d,%d,z=%.2f) region='%s' turning=%d",
+			_scene.state.roomName.c_str(), _playerState.centerX, _playerState.bottomY,
+			_playerState.z, _playerState.targetX, _playerState.targetBottomY,
+			_playerState.targetZ, _pendingRegionName.c_str(), _playerState.turnActive);
+	}
 	if (interaction.requestDemoEnding) {
 		Common::Error endingError = _flow.runDemoEnding();
 		if (endingError.getCode() != Common::kNoError)
@@ -295,7 +303,7 @@ Common::Error RoomInteractionProcessor::handleInteractionResult(const Interactio
 				interaction.continuationTag, continuationInteraction, true,
 				_scene.state.roomName)) {
 			Common::Error interactionError =
-				handleInteractionResult(continuationInteraction, didTransition, usedItemName);
+				handleInteractionResult(continuationInteraction, didTransition, usedItemName, interruptPlayerMovement);
 			if (interactionError.getCode() != Common::kNoError)
 				return interactionError;
 		}
diff --git a/engines/harvester/room_interaction.h b/engines/harvester/room_interaction.h
index 28184482c34..0fcdb578441 100644
--- a/engines/harvester/room_interaction.h
+++ b/engines/harvester/room_interaction.h
@@ -62,7 +62,7 @@ public:
 		bool canExitCloseupToParent, RoomInteractionCallbacks &callbacks);
 
 	Common::Error handleInteractionResult(const InteractionResult &interaction,
-		bool &didTransition, const Common::String &usedItemName);
+		bool &didTransition, const Common::String &usedItemName, bool interruptPlayerMovement = true);
 	Common::Error runScriptedDialogue(const Common::String &npcName, const Common::String &usedItemName,
 		const Common::String &continuationTag, bool &didTransition);
 


Commit: 4e05e6800f9fc37a8ab40d92fcee98ab1ebc2823
    https://github.com/scummvm/scummvm/commit/4e05e6800f9fc37a8ab40d92fcee98ab1ebc2823
Author: Alex Bevilacqua (alex at alexbevi.com)
Date: 2026-10-07T06:22:19-04:00

Commit Message:
HARVESTER: Improve animation synchronization

Fixes #17297

Assisted-by: Codex:gpt-6.1-sol

Changed paths:
    engines/harvester/room.cpp
    engines/harvester/runtime_entity.cpp


diff --git a/engines/harvester/room.cpp b/engines/harvester/room.cpp
index a4ed59b22cc..5d07e06f069 100644
--- a/engines/harvester/room.cpp
+++ b/engines/harvester/room.cpp
@@ -4372,6 +4372,12 @@ Common::Error RoomSystem::runRoomLoop(Flow &flow, const Common::String &targetNa
 			}
 		}
 
+		// At 60 Hz, consume animation advances before the next screen update so
+		// a walk frame and its movement step are presented together.
+		if (flow.tickRuntimeEntities())
+			needsRedraw = true;
+		syncAnimatedRoomActorPlacement();
+
 		Common::Error combatError = Common::kNoError;
 		if (!playerControlPaused) {
 			combatError = resolvePlayerAttackContact();
@@ -4463,9 +4469,6 @@ Common::Error RoomSystem::runRoomLoop(Flow &flow, const Common::String &targetNa
 				return Common::kReadingFailed;
 			break;
 		}
-		if (flow.tickRuntimeEntities())
-			needsRedraw = true;
-		syncAnimatedRoomActorPlacement();
 		if (Player::updateDeathAnimationState(playerState)) {
 			requestPlayerGameOver("combat_player_death_complete", Common::String());
 			needsRedraw = true;
diff --git a/engines/harvester/runtime_entity.cpp b/engines/harvester/runtime_entity.cpp
index b6607d08c1e..6b8b08f46ee 100644
--- a/engines/harvester/runtime_entity.cpp
+++ b/engines/harvester/runtime_entity.cpp
@@ -591,6 +591,11 @@ void Entity::draw(Graphics::Screen &screen) const {
 		return;
 
 	const Common::Point drawOrigin = getDrawOrigin();
+	if (_classId == kRuntimeEntityClassPlayer) {
+		debugC(4, kDebugPlayer,
+			"Harvester: player animation draw frame=%d pos=(%d,%d,z=%.2f) origin=(%d,%d) tick=%u",
+			_currentFrame, _x, _y, _z, drawOrigin.x, drawOrigin.y, getRuntimeClockTicks());
+	}
 	blitAnimationFrame(screen, _frames, _currentFrame, drawOrigin.x, drawOrigin.y);
 }
 


Commit: 4e6af2292f8570fe3c8012834b4b2fc637b620e1
    https://github.com/scummvm/scummvm/commit/4e6af2292f8570fe3c8012834b4b2fc637b620e1
Author: Alex Bevilacqua (alex at alexbevi.com)
Date: 2026-10-07T06:22:19-04:00

Commit Message:
HARVESTER: Preserve player sprite bases across animation frames

Assisted-by: Codex:gpt-6.1-sol

Changed paths:
    engines/harvester/player.cpp
    engines/harvester/player.h
    engines/harvester/room.cpp


diff --git a/engines/harvester/player.cpp b/engines/harvester/player.cpp
index 3f058e74409..720dbcb0ab2 100644
--- a/engines/harvester/player.cpp
+++ b/engines/harvester/player.cpp
@@ -593,17 +593,7 @@ static int computeRoomPlayerVerticalScreenStep() {
 }
 
 static int computePlayerFrameLeftX(const RoomPlayerState &playerState) {
-	if (!playerState.entity)
-		return playerState.centerX;
-
-	int width = 0;
-	int height = 0;
-	int xOffset = 0;
-	int yOffset = 0;
-	if (!playerState.entity->getCurrentFrameMetrics(width, height, xOffset, yOffset))
-		return playerState.centerX;
-
-	return playerState.centerX - width / 2;
+	return playerState.entity ? playerState.entity->getScreenRect().left : playerState.centerX;
 }
 
 static void resetPlayerMoveTargetProgress(RoomPlayerState &playerState) {
@@ -779,6 +769,17 @@ static void reinsertPlayerSceneEntity(HarvesterEngine &engine, const RoomPlayerS
 	}
 }
 
+static void applyPlayerMovementPlacement(const RoomSetupState &state, RoomPlayerState &playerState,
+		int candidateCenterX, int candidateBottomY, float candidateZ) {
+	Entity &entity = *playerState.entity;
+	// advance_entity_animation_frame (0x4c8a0) moves the pre-offset screen base.
+	// rescale_entity_sprite_for_depth (0x4b440) changes bounds without recentering it.
+	const float renderZ = candidateZ - floorf(MAX<float>(entity.getZExtent(), 0.0f) * 0.5f);
+	entity.setPosition(entity.getX() + candidateCenterX - playerState.centerX,
+		entity.getY() + candidateBottomY - playerState.bottomY, renderZ);
+	entity.setDepthScale(Player::computeDepthScale(state, renderZ));
+}
+
 static bool isPlayerMovementBlocked(HarvesterEngine &engine, const RoomSetupState &state,
 		const Common::Array<ObjectRecord> &sceneObjects,
 		const Common::Array<AnimRecord> &sceneAnimations,
@@ -790,19 +791,17 @@ static bool isPlayerMovementBlocked(HarvesterEngine &engine, const RoomSetupStat
 	if (blockerOut)
 		*blockerOut = nullptr;
 
-	const int oldCenterX = playerState.centerX;
-	const int oldBottomY = playerState.bottomY;
-	const float oldZ = playerState.z;
-	candidateBottomY = clampRoomMovementY(state, candidateBottomY);
-	if (!applyRoomActorPlacement(state, *playerState.entity,
-			candidateCenterX, candidateBottomY, candidateZ)) {
-		(void)applyRoomActorPlacement(state, *playerState.entity, oldCenterX, oldBottomY, oldZ);
-		return true;
-	}
+	Entity &entity = *playerState.entity;
+	const int oldX = entity.getX();
+	const int oldY = entity.getY();
+	const float oldZ = entity.getZ();
+	const float oldScale = entity.getDepthScale();
+	applyPlayerMovementPlacement(state, playerState, candidateCenterX, candidateBottomY, candidateZ);
 
 	const Entity *blocker = findPlayerRoomBlocker(engine, sceneObjects, sceneAnimations,
-		*playerState.entity, ignoredBlockerName);
-	(void)applyRoomActorPlacement(state, *playerState.entity, oldCenterX, oldBottomY, oldZ);
+		entity, ignoredBlockerName);
+	entity.setPosition(oldX, oldY, oldZ);
+	entity.setDepthScale(oldScale);
 	if (blockerOut)
 		*blockerOut = blocker;
 	return blocker != nullptr;
@@ -820,7 +819,6 @@ static bool tryApplyPlayerMovement(HarvesterEngine &engine, const RoomSetupState
 		*blockerOut = nullptr;
 
 	candidateCenterX = clampPlayerCenterXToNativeBounds(playerState, candidateCenterX);
-	candidateBottomY = clampRoomMovementY(state, candidateBottomY);
 	candidateZ = clampRoomDepth(state, candidateZ);
 	if (candidateCenterX == playerState.centerX &&
 			candidateBottomY == playerState.bottomY &&
@@ -833,13 +831,9 @@ static bool tryApplyPlayerMovement(HarvesterEngine &engine, const RoomSetupState
 		return false;
 	}
 
-	playerState.centerX = candidateCenterX;
-	playerState.bottomY = candidateBottomY;
+	applyPlayerMovementPlacement(state, playerState, candidateCenterX, candidateBottomY, candidateZ);
 	playerState.z = candidateZ;
-	if (!applyRoomActorPlacement(state, *playerState.entity,
-			playerState.centerX, playerState.bottomY, playerState.z)) {
-		return false;
-	}
+	Player::syncAnimationPlacement(playerState);
 
 	reinsertPlayerSceneEntity(engine, playerState);
 	return true;
@@ -1120,6 +1114,22 @@ static bool finishPlayerIdleAnimation(const RoomSetupState &state, RoomPlayerSta
 
 } // End of anonymous namespace
 
+void Player::syncAnimationPlacement(RoomPlayerState &playerState) {
+	if (!playerState.entity)
+		return;
+
+	const Common::Rect rect = playerState.entity->getScreenRect();
+	const int centerX = rect.left + rect.width() / 2;
+	if (playerState.centerX != centerX || playerState.bottomY != rect.bottom) {
+		debugC(3, kDebugPlayer,
+			"Harvester: player frame bounds frame=%d base=(%d,%d) rect=(%d,%d)-(%d,%d) z=%.2f",
+			playerState.entity->getCurrentFrame(), playerState.entity->getX(), playerState.entity->getY(),
+			rect.left, rect.top, rect.right, rect.bottom, (double)playerState.z);
+	}
+	playerState.centerX = centerX;
+	playerState.bottomY = rect.bottom;
+}
+
 float Player::computeDepthScale(const RoomSetupState &state, float z) {
 	float scale = 1.0f;
 	if (state.roomPerspectiveScale != 0.0f) {
@@ -1760,8 +1770,7 @@ bool Player::stepMoveTarget(HarvesterEngine &engine, const RoomSetupState &state
 			? playerState.z >= (float)state.roomMaxZ
 			: playerState.z <= (float)state.roomMinZ;
 		if (!depthBoundaryReached) {
-			candidateBottomY = clampRoomMovementY(state,
-				playerState.bottomY + depthDirection * verticalStep);
+			candidateBottomY = playerState.bottomY + depthDirection * verticalStep;
 			candidateZ = regionFacing == 0 || regionFacing == 3
 				? clampRoomDepth(state, playerState.z - depthDirection * depthStep)
 				: stepTowardsFloat(playerState.z, playerState.targetZ, depthStep);
@@ -1813,9 +1822,9 @@ bool Player::stepMoveTarget(HarvesterEngine &engine, const RoomSetupState &state
 		return setIdleAnimation(playerState, idleFacing);
 	}
 
-	const int actualFacing = resolveFacingFromRoomMovement(
-		previousCenterX, previousBottomY, playerState.centerX, playerState.bottomY);
+	const int actualFacing = desiredFacing;
 	(void)setPlayerWalkAnimation(playerState, actualFacing);
+	syncAnimationPlacement(playerState);
 	refreshPlayerCurrentBlocker(engine, sceneObjects, sceneAnimations, playerState);
 	if (engine.isPathfindingDebugEnabled()) {
 		debugC(1, kDebugPathfinding,
@@ -1873,6 +1882,8 @@ bool Player::stepKeyboardMovement(HarvesterEngine &engine, const RoomSetupState
 		return setIdleAnimation(playerState, playerState.facing);
 	}
 
+	const int previousBaseX = playerState.entity->getX();
+	const int previousBaseY = playerState.entity->getY();
 	const int previousCenterX = playerState.centerX;
 	const int previousBottomY = playerState.bottomY;
 	const float previousZ = playerState.z;
@@ -1885,15 +1896,12 @@ bool Player::stepKeyboardMovement(HarvesterEngine &engine, const RoomSetupState
 	float candidateZ = playerState.z;
 	bool depthBoundaryReached = false;
 	if (verticalInput != 0) {
-		// update_actor_runtime_state (0x4d750) only emits the paired screen-Y and Z
-		// steps for state 0x04 while min_z < z and state 0x0b while z < max_z.
-		// At either depth limit it zeros both steps and returns to the facing idle state.
+
 		depthBoundaryReached = verticalInput < 0
 			? playerState.z >= (float)state.roomMaxZ
 			: playerState.z <= (float)state.roomMinZ;
 		if (!depthBoundaryReached) {
-			candidateBottomY = clampRoomMovementY(state,
-				playerState.bottomY + verticalInput * verticalStep);
+			candidateBottomY = playerState.bottomY + verticalInput * verticalStep;
 			candidateZ = clampRoomDepth(state, playerState.z - verticalInput * depthStep);
 		}
 	}
@@ -1934,7 +1942,7 @@ bool Player::stepKeyboardMovement(HarvesterEngine &engine, const RoomSetupState
 		return setIdleAnimation(playerState, playerState.facing >= 0 ? playerState.facing : 0);
 
 	const int actualFacing = resolveFacingFromRoomMovement(
-		previousCenterX, previousBottomY, playerState.centerX, playerState.bottomY);
+		previousBaseX, previousBaseY, playerState.entity->getX(), playerState.entity->getY());
 	(void)setPlayerWalkAnimation(playerState, actualFacing);
 	if (keyboardWalking && currentFrame == range.walkLastFrame) {
 		playerState.keyboardWalkRequest = -1;
@@ -1943,6 +1951,7 @@ bool Player::stepKeyboardMovement(HarvesterEngine &engine, const RoomSetupState
 			"Harvester: player keyboard walk bank restart facing=%d frame=%d",
 			actualFacing, playerState.entity->getCurrentFrame());
 	}
+	syncAnimationPlacement(playerState);
 	debugC(2, kDebugPlayer,
 		"Harvester: player keyboard move room='%s' input=(%d,%d) step=(%d,%d) pos=(%d,%d,z=%.2f) facing=%d frame=%d moved=%d",
 		state.roomName.c_str(), (moveRight ? 1 : 0) - (moveLeft ? 1 : 0),
diff --git a/engines/harvester/player.h b/engines/harvester/player.h
index f3d0f102f0a..bdf7d89649a 100644
--- a/engines/harvester/player.h
+++ b/engines/harvester/player.h
@@ -28,6 +28,7 @@ namespace Harvester {
 
 class Player {
 public:
+	static void syncAnimationPlacement(RoomPlayerState &playerState);
 	static float computeDepthScale(const RoomSetupState &state, float z);
 	static int resolveFacingFrame(int facing);
 	static const char *describeCombatLoadout(int loadout);
diff --git a/engines/harvester/room.cpp b/engines/harvester/room.cpp
index 5d07e06f069..c8cbdd621e2 100644
--- a/engines/harvester/room.cpp
+++ b/engines/harvester/room.cpp
@@ -630,6 +630,7 @@ Common::Error RoomSystem::runRoomLoop(Flow &flow, const Common::String &targetNa
 			}
 		};
 		auto captureCurrentSaveState = [&]() {
+			Player::syncAnimationPlacement(playerState);
 			syncCurrentRoomRuntimeState();
 			const int facing = playerState.facing >= 0 ? playerState.facing : scene.state.playerFacing;
 			_engine.captureCurrentSaveRoomState(scene.state.entranceName, scene.state.roomName,
@@ -1382,6 +1383,7 @@ Common::Error RoomSystem::runRoomLoop(Flow &flow, const Common::String &targetNa
 			playerState.turnTargetFacing = -1;
 			if (playerState.entity && playerState.facing >= 0)
 				(void)Player::setIdleAnimation(playerState, playerState.facing);
+			Player::syncAnimationPlacement(playerState);
 		};
 		auto refreshCurrentScene = [&](bool preservePlayerPlacement) {
 			Common::Array<AudioCommand> entryAudioCommands = scene.state.audioCommands;
@@ -1451,10 +1453,7 @@ Common::Error RoomSystem::runRoomLoop(Flow &flow, const Common::String &targetNa
 			return _inventory.refresh();
 		};
 		auto syncAnimatedRoomActorPlacement = [&]() {
-			if (playerState.entity) {
-				(void)applyRoomActorPlacement(scene.state, *playerState.entity,
-					playerState.centerX, playerState.bottomY, playerState.z);
-			}
+			Player::syncAnimationPlacement(playerState);
 			if (!entityManager)
 				return;
 


Commit: 78d34939f7fb9eb0e0224309a5276369f2d48f5d
    https://github.com/scummvm/scummvm/commit/78d34939f7fb9eb0e0224309a5276369f2d48f5d
Author: Alex Bevilacqua (alex at alexbevi.com)
Date: 2026-10-07T06:22:19-04:00

Commit Message:
HARVESTER: Adjust target depth during sideways walking

Assisted-by: Codex:gpt-6.1-sol

Changed paths:
    engines/harvester/player.cpp


diff --git a/engines/harvester/player.cpp b/engines/harvester/player.cpp
index 720dbcb0ab2..53b7a8bbbdc 100644
--- a/engines/harvester/player.cpp
+++ b/engines/harvester/player.cpp
@@ -1724,7 +1724,7 @@ bool Player::stepMoveTarget(HarvesterEngine &engine, const RoomSetupState &state
 		}
 		horizontalDirection = 0;
 	}
-	int depthDirection = horizontalDirection == 0 && !playerState.moveTargetZReached
+	int depthDirection = !playerState.moveTargetZReached
 		? resolveMoveTargetDepthDirection(playerState, regionFacing)
 		: 0;
 	if (depthDirection != 0 && isDepthDirectionBlockedByHistory(playerState, depthDirection)) {
@@ -1763,6 +1763,7 @@ bool Player::stepMoveTarget(HarvesterEngine &engine, const RoomSetupState &state
 		const int horizontalStep = computeRoomPlayerHorizontalStep(state, playerState.z);
 		candidateCenterX = clampPlayerCenterXToNativeBounds(
 			playerState, playerState.centerX + horizontalDirection * horizontalStep);
+		candidateZ = clampRoomDepth(state, playerState.z - depthDirection);
 	} else if (depthDirection != 0) {
 		const int verticalStep = computeRoomPlayerVerticalScreenStep();
 		const float depthStep = computeRoomPlayerDepthStep(state);


Commit: 05e5e68b42bc0abcbdb0829efa655e72c296a4d5
    https://github.com/scummvm/scummvm/commit/05e5e68b42bc0abcbdb0829efa655e72c296a4d5
Author: Alex Bevilacqua (alex at alexbevi.com)
Date: 2026-10-07T06:22:19-04:00

Commit Message:
HARVESTER: adjust player/npc animation frame logging

Changed paths:
    engines/harvester/runtime_entity.cpp


diff --git a/engines/harvester/runtime_entity.cpp b/engines/harvester/runtime_entity.cpp
index 6b8b08f46ee..7f5086a0754 100644
--- a/engines/harvester/runtime_entity.cpp
+++ b/engines/harvester/runtime_entity.cpp
@@ -455,8 +455,8 @@ bool Entity::tickVisualState(uint32 now) {
 	_nextAnimationTick = now + _animationTickInterval;
 	_animationAdvancedLastTick = true;
 
-	if (_classId == kRuntimeEntityClassPlayer) {
-		debugC(3, kDebugPlayer,
+	if (_classId == kRuntimeEntityClassPlayer && previousFrameIndex != _currentFrame) {
+		debugC(4, kDebugPlayer,
 			"Harvester: player animation advance frame=%d->%d range=%d..%d rate=%d tick=%u interval=%u next_tick=%u",
 			previousFrameIndex, _currentFrame, _firstFrame, _lastFrame,
 			_animationRate, now, _animationTickInterval, _nextAnimationTick);
@@ -468,7 +468,7 @@ bool Entity::tickVisualState(uint32 now) {
 		const bool loopReset = _looping && !_pingPong &&
 			((!wasPlayingBackwards && previousFrameIndex == _lastFrame && _currentFrame == _firstFrame) ||
 			 (wasPlayingBackwards && previousFrameIndex == _firstFrame && _currentFrame == _lastFrame));
-		debugC(3, kDebugPlayer,
+		debugC(4, kDebugPlayer,
 			"Harvester: npc animation advance npc='%s' frame=%d->%d range=%d..%d loop_reset=%d backwards=%d->%d rate=%d interval=%u entity=(%d,%d,z=%.2f) previous=(size=%ux%u offset=%d,%d draw=%d,%d) current=(size=%ux%u offset=%d,%d draw=%d,%d)",
 			_name.c_str(),
 			previousFrameIndex, _currentFrame, _firstFrame, _lastFrame,


Commit: af5340ebc0a315a462969277d242cb9ded5e2154
    https://github.com/scummvm/scummvm/commit/af5340ebc0a315a462969277d242cb9ded5e2154
Author: Alex Bevilacqua (alex at alexbevi.com)
Date: 2026-10-07T06:22:19-04:00

Commit Message:
HARVESTER: adjust palette fade logging verbosity

Changed paths:
    engines/harvester/flow.cpp


diff --git a/engines/harvester/flow.cpp b/engines/harvester/flow.cpp
index a7d0c987abd..955890d4e88 100644
--- a/engines/harvester/flow.cpp
+++ b/engines/harvester/flow.cpp
@@ -2507,7 +2507,7 @@ bool Flow::waitForPaletteFadeTick(uint32 startTick, Common::Error &result) {
 			return true;
 		g_system->delayMillis(1);
 	}
-	debugC(3, kDebugScene,
+	debugC(4, kDebugScene,
 		"Harvester: palette fade step tick=%u->%u interval=%u",
 		startTick, getRuntimeClockTicks(), kPaletteFadeIntervalTicks);
 	return false;


Commit: 6793425f01a0acb000ea336f2a96ed9d433e8aba
    https://github.com/scummvm/scummvm/commit/6793425f01a0acb000ea336f2a96ed9d433e8aba
Author: Alex Bevilacqua (alex at alexbevi.com)
Date: 2026-10-07T06:22:19-04:00

Commit Message:
HARVESTER: fix menu options overlap on game over

Fixes #17290

Assisted-by: Codex:gpt-6.1-sol

Changed paths:
    engines/harvester/menu.cpp


diff --git a/engines/harvester/menu.cpp b/engines/harvester/menu.cpp
index 1fd4fc5c7a1..a788bad1412 100644
--- a/engines/harvester/menu.cpp
+++ b/engines/harvester/menu.cpp
@@ -977,8 +977,11 @@ Common::Error MenuSystem::runMainMenuStub(Flow &flow) {
 
 		if (selectedItem == kMainMenuItemOptions) {
 			IndexedBitmap menuBackdrop;
-			if (!captureMenuBackdrop(menuBackdrop))
+			if (_hasMainMenuBackdrop) {
+				menuBackdrop = _mainMenuBackdrop;
+			} else if (!captureMenuBackdrop(menuBackdrop)) {
 				return Common::kReadingFailed;
+			}
 			Common::Error optionsError = runOptionsMenu(menuBackdrop, menuPalette, 1.0f, flow);
 			needsRedraw = true;
 			return optionsError;




More information about the Scummvm-git-logs mailing list