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

neuromancer noreply at scummvm.org
Thu Jun 4 15:12:23 UTC 2026


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

Summary:
31a97081b0 SCUMM: RA1: proper indicator rendering for level 8
6748744720 SCUMM: RA1: proper winning condition for level 8
0d51b3e9c2 SCUMM: RA1: adding more missing wining conditions
b820b7a630 SCUMM: RA1: improve shift + S to skip some sequences that have wining conditions
d9fc895bf5 SCUMM: RA1: added missing overlay in level 5


Commit: 31a97081b053069f9b6437c87f199444617ec61e
    https://github.com/scummvm/scummvm/commit/31a97081b053069f9b6437c87f199444617ec61e
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-06-04T15:19:15+02:00

Commit Message:
SCUMM: RA1: proper indicator rendering for level 8

Changed paths:
    engines/scumm/smush/rebel/smush_player_ra1.cpp


diff --git a/engines/scumm/smush/rebel/smush_player_ra1.cpp b/engines/scumm/smush/rebel/smush_player_ra1.cpp
index aa213b17a65..880eb987be9 100644
--- a/engines/scumm/smush/rebel/smush_player_ra1.cpp
+++ b/engines/scumm/smush/rebel/smush_player_ra1.cpp
@@ -241,11 +241,13 @@ bool SmushPlayerRebel1::handleGameFetch(int32 subSize, Common::SeekableReadStrea
 			if (rebel1->isInteractiveVideoActive()) {
 				const uint16 gameOp = rebel1->getActiveGameOpcode();
 				const bool fullWidthStoredPatch = (_storedFobjWidth == _vm->_screenWidth);
-				// 0x0B (asteroid/surface) and 0x19/0x1A (on-foot) use SetCameraOffset
-				// directly — no projection-based FTCH placement. Level 4 phase 2
-				// also stores a full-width screen-space patch (320x180) that DOS
-				// restores without the centered 1/4 projection warp.
-				if (fullWidthStoredPatch || gameOp == 0x0B ||
+				const bool level8WalkerPatch = (gameOp == 0x0B && rebel1->getCurrentLevel() == 7);
+				// Keep the direct viewport placement used by the existing 0x0B
+				// compatibility path, except for Level 8's walker cockpit patch.
+				// DOS FTCH goes through FUN_28D0A, which sets DispatchFobjCodec
+				// flag 0x800; Level 8 depends on that quarter-projection so the
+				// cockpit and RunLevel8Flow indicators move together.
+				if (fullWidthStoredPatch || (gameOp == 0x0B && !level8WalkerPatch) ||
 						gameOp == 0x19 || gameOp == 0x1A) {
 					left += _ra1ViewportOffsetX;
 					top += _ra1ViewportOffsetY;


Commit: 67487447204cab06b657be2eb19eaa42a7d56781
    https://github.com/scummvm/scummvm/commit/67487447204cab06b657be2eb19eaa42a7d56781
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-06-04T15:30:13+02:00

Commit Message:
SCUMM: RA1: proper winning condition for level 8

Changed paths:
    engines/scumm/insane/rebel1/iact.cpp
    engines/scumm/insane/rebel1/rebel.cpp
    engines/scumm/insane/rebel1/rebel.h
    engines/scumm/insane/rebel1/runlevels.cpp


diff --git a/engines/scumm/insane/rebel1/iact.cpp b/engines/scumm/insane/rebel1/iact.cpp
index 8baa313f60f..48f1ff485ee 100644
--- a/engines/scumm/insane/rebel1/iact.cpp
+++ b/engines/scumm/insane/rebel1/iact.cpp
@@ -2110,6 +2110,8 @@ void InsaneRebel1::handleGameOpcode5EReset(uint32 param1) {
 		return;
 	}
 
+	const int16 walkerReplayKillCount = (_walkerRoundReplay && _currentLevel == 7) ? _killCount : 0;
+
 	_damageFlags = 0;
 	_prevDamageFlags = 0;
 	_damageCooldown = 0;
@@ -2164,6 +2166,8 @@ void InsaneRebel1::handleGameOpcode5EReset(uint32 param1) {
 	_gostSlotIdx = 0;
 	_killCount = 0;
 	_lastHitTarget = 0;
+	if (_walkerRoundReplay && _currentLevel == 7)
+		_killCount = walkerReplayKillCount;
 
 	// Field1 == 0 corresponds to baseline recenter behavior in the original.
 	if ((int32)param1 == 0) {
diff --git a/engines/scumm/insane/rebel1/rebel.cpp b/engines/scumm/insane/rebel1/rebel.cpp
index 248b9ae29c5..cdbf2d37e32 100644
--- a/engines/scumm/insane/rebel1/rebel.cpp
+++ b/engines/scumm/insane/rebel1/rebel.cpp
@@ -418,6 +418,7 @@ InsaneRebel1::InsaneRebel1(ScummEngine_v7 *scumm) : Insane(), _vm(scumm) {
 	_walkerHealth = 100;
 	_walkerTimer = 0;
 	_walkerBranchChoice = 0;
+	_walkerRoundReplay = false;
 	resetFrameObjectState();
 
 	if (loadRA1Nut("SYS/TALKFONT.NUT", _hudFontBank)) {
diff --git a/engines/scumm/insane/rebel1/rebel.h b/engines/scumm/insane/rebel1/rebel.h
index 4d1478e66ae..23570cc8d13 100644
--- a/engines/scumm/insane/rebel1/rebel.h
+++ b/engines/scumm/insane/rebel1/rebel.h
@@ -694,6 +694,7 @@ private:
 	int16 _walkerHealth;     // Walker health percentage (0-100), init=100
 	int16 _walkerTimer;      // Attack window countdown (100→0)
 	int16 _walkerBranchChoice; // Directional choice: 0=none, 1=left, 2=right
+	bool _walkerRoundReplay; // Preserve walker damage across a replayed L8 route
 
 	// Attack window frame numbers per route (3 routes × 3 windows)
 	// Route 0: 2588/1709/262, Route 1: 2323/1444/-2, Route 2: 877/-2/-2
diff --git a/engines/scumm/insane/rebel1/runlevels.cpp b/engines/scumm/insane/rebel1/runlevels.cpp
index 1b7c05a8a0a..35a01e30779 100644
--- a/engines/scumm/insane/rebel1/runlevels.cpp
+++ b/engines/scumm/insane/rebel1/runlevels.cpp
@@ -821,15 +821,19 @@ bool InsaneRebel1::runLevel8() {
 		_walkerHealth = 100;
 		_walkerTimer = 0;
 		_walkerBranchChoice = 0;
+		_walkerRoundReplay = false;
 
 		int route = 0;
 		int32 routeStartFrame = 0;
+		bool replayingRound = false;
 		while (!shouldAbortGameFlow()) {
 			_levelRouteIndex = route;
 			_pendingRouteIndex = -1;
 			_pendingRouteStartFrame = routeStartFrame;
 			_pendingRouteCutoverFrame = -1;
+			_walkerRoundReplay = replayingRound;
 			playInteractiveVideo(kLevel8Routes[route], routeStartFrame);
+			_walkerRoundReplay = false;
 			if (shouldAbortGameFlow())
 				return false;
 
@@ -839,23 +843,39 @@ bool InsaneRebel1::runLevel8() {
 			if (_walkerHealth <= 0)
 				break;
 
-			if (_pendingRouteIndex < 0 || _pendingRouteIndex == route)
-				break;
+			if (_pendingRouteIndex >= 0 && _pendingRouteIndex != route) {
+				// RunLevel8Flow uses PlayAnmFile(..., g_frameCounter, 1, -1)
+				// when it branches from one walker route to another. That wrapper
+				// keeps the current ANM alive for seven more gameplay frames, then
+				// opens the destination route while preserving the active state.
+				routeStartFrame = _pendingRouteStartFrame;
+				route = _pendingRouteIndex;
+				replayingRound = false;
+				continue;
+			}
 
-			// RunLevel8Flow uses PlayAnmFile(..., g_frameCounter, 1, -1)
-			// when it branches from one walker route to another. That wrapper
-			// keeps the current ANM alive for seven more gameplay frames, then
-			// opens the destination route while preserving the active state.
-			routeStartFrame = _pendingRouteStartFrame;
-			route = _pendingRouteIndex;
+			// RunLevel8Flow keeps pumping the active route while the walker
+			// shield register is nonzero. ScummVM's blocking SMUSH play returns
+			// when one route pass ends, so explicitly replay that route and
+			// preserve the accumulated walker damage.
+			debugC(DEBUG_INSANE, "RA1 L8 replaying route=%d walkerHealth=%d killCount=%d",
+				route, (int)_walkerHealth, (int)_killCount);
+			routeStartFrame = 0;
+			_walkerTimer = 0;
+			_walkerBranchChoice = 0;
+			replayingRound = true;
 		}
 
 		_levelRouteIndex = -1;
 		_pendingRouteIndex = -1;
 		_pendingRouteStartFrame = 0;
 		_pendingRouteCutoverFrame = -1;
+		_walkerRoundReplay = false;
 
-		if (_health >= 0) {
+		if (shouldAbortGameFlow())
+			return false;
+
+		if (_health >= 0 && _walkerHealth <= 0) {
 			playChapterCompleteCinematic("LVL8/L8END.ANM", 8, 0x5F, 5);
 			return !shouldAbortGameFlow();
 		}


Commit: 0d51b3e9c2a701daa9c0bb3f22d13c9da0eb1a1b
    https://github.com/scummvm/scummvm/commit/0d51b3e9c2a701daa9c0bb3f22d13c9da0eb1a1b
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-06-04T16:38:32+02:00

Commit Message:
SCUMM: RA1: adding more missing wining conditions

Changed paths:
    engines/scumm/insane/rebel1/rebel.cpp
    engines/scumm/insane/rebel1/rebel.h
    engines/scumm/insane/rebel1/runlevels.cpp


diff --git a/engines/scumm/insane/rebel1/rebel.cpp b/engines/scumm/insane/rebel1/rebel.cpp
index cdbf2d37e32..4cfee698554 100644
--- a/engines/scumm/insane/rebel1/rebel.cpp
+++ b/engines/scumm/insane/rebel1/rebel.cpp
@@ -317,6 +317,7 @@ InsaneRebel1::InsaneRebel1(ScummEngine_v7 *scumm) : Insane(), _vm(scumm) {
 	_maxChapterUnlocked = 0;
 	_unlockAllLevels = ConfMan.getBool("rebel1_unlock_all");
 	_interactiveVideoActive = false;
+	_preserveInteractiveRuntimeState = false;
 	_restoreInteractiveVideoAudioState = false;
 	memset(_savedInteractiveVideoTrackState, 0, sizeof(_savedInteractiveVideoTrackState));
 	memset(_savedInteractiveVideoTrackGroupId, 0, sizeof(_savedInteractiveVideoTrackGroupId));
diff --git a/engines/scumm/insane/rebel1/rebel.h b/engines/scumm/insane/rebel1/rebel.h
index 23570cc8d13..8ff100263bc 100644
--- a/engines/scumm/insane/rebel1/rebel.h
+++ b/engines/scumm/insane/rebel1/rebel.h
@@ -521,6 +521,7 @@ private:
 
 	// True only while an interactive gameplay SMUSH is running.
 	bool _interactiveVideoActive;
+	bool _preserveInteractiveRuntimeState;
 
 	// Path branching for levels with left/right alternative videos.
 	// Original sets nextSceneA/nextSceneB when GAME 0x07 counter == 394 (0x18A).
diff --git a/engines/scumm/insane/rebel1/runlevels.cpp b/engines/scumm/insane/rebel1/runlevels.cpp
index 35a01e30779..7f4e7ce0d41 100644
--- a/engines/scumm/insane/rebel1/runlevels.cpp
+++ b/engines/scumm/insane/rebel1/runlevels.cpp
@@ -551,13 +551,35 @@ bool InsaneRebel1::runLevel4() {
 		_shieldGenHitsA = 0;
 		_shieldGenHitsB = 0;
 		_levelGameplayPhase = 1;
-		playInteractiveVideo("LVL4/L4PLAY1.ANM");
+		bool shieldGeneratorsDestroyed = false;
+		bool replayingShieldGeneratorPhase = false;
+		while (!shouldAbortGameFlow()) {
+			_preserveInteractiveRuntimeState = replayingShieldGeneratorPhase;
+			playInteractiveVideo("LVL4/L4PLAY1.ANM");
+			_preserveInteractiveRuntimeState = false;
+			if (shouldAbortGameFlow())
+				return false;
+
+			if (_health < 0)
+				break;
+
+			shieldGeneratorsDestroyed =
+				_protectedTargetA == 0 && _protectedTargetB == 0 &&
+				_shieldGenHitsA > 0x30 + 0x3C && _shieldGenHitsB > 0x30;
+			if (shieldGeneratorsDestroyed)
+				break;
+
+			debugC(DEBUG_INSANE, "RA1 L4 replaying shield-generator phase: hits=(%d,%d) protected=(%d,%d)",
+				(int)_shieldGenHitsA, (int)_shieldGenHitsB,
+				(int)_protectedTargetA, (int)_protectedTargetB);
+			replayingShieldGeneratorPhase = true;
+		}
 		_protectedTargetA = 0;
 		_protectedTargetB = 0;
 		if (shouldAbortGameFlow())
 			return false;
 
-		if (_health >= 0) {
+		if (_health >= 0 && shieldGeneratorsDestroyed) {
 			// Phase 2: torpedo run. The DOS loop enables torpedo mode at frame
 			// 0x3E and exits early as soon as killCount becomes nonzero.
 			_activeGameOpcode = 0;
@@ -572,7 +594,7 @@ bool InsaneRebel1::runLevel4() {
 				return false;
 		}
 
-		if (_health >= 0) {
+		if (_health >= 0 && shieldGeneratorsDestroyed) {
 			// L4END1 = torpedo hit, L4END2 = torpedo missed
 			const bool torpedoHit = (_killCount != 0);
 			playChapterCompleteCinematic(torpedoHit ? "LVL4/L4END1.ANM" : "LVL4/L4END2.ANM",
@@ -1290,11 +1312,31 @@ bool InsaneRebel1::runLevel14() {
 		_level14SuccessFrames = 0;
 
 		// Phase 1: targeting surface cannons
-		playInteractiveVideo("LVL14/L14PLAY.ANM");
+		bool level14Phase1Complete = false;
+		bool replayingLevel14Phase1 = false;
+		while (!shouldAbortGameFlow()) {
+			_preserveInteractiveRuntimeState = replayingLevel14Phase1;
+			playInteractiveVideo("LVL14/L14PLAY.ANM");
+			_preserveInteractiveRuntimeState = false;
+			if (shouldAbortGameFlow())
+				return false;
+
+			if (_health < 0)
+				break;
+
+			level14Phase1Complete = _level14SuccessFrames >= 0x3C;
+			if (level14Phase1Complete)
+				break;
+
+			debugC(DEBUG_INSANE, "RA1 L14 replaying phase 1: successFrames=%d targetsDestroyed=%d",
+				(int)_level14SuccessFrames, areLevel14Phase1TargetsDestroyed() ? 1 : 0);
+			replayingLevel14Phase1 = true;
+		}
 		if (shouldAbortGameFlow())
 			return false;
 
-		if (_health >= 0) {
+		bool level14Phase2Complete = false;
+		if (_health >= 0 && level14Phase1Complete) {
 			// Phase 2: exhaust port approach
 			_activeGameOpcode = 0;
 			_gameLatch5D = 0;
@@ -1308,25 +1350,48 @@ bool InsaneRebel1::runLevel14() {
 			_level14Play2BSpliced = false;
 			_level14Play2BSpliceFrame = 0;
 
-			playInteractiveVideo("LVL14/L14PLAY2.ANM");
-			if (shouldAbortGameFlow())
-				return false;
-
-			if (_health >= 0 && _level14Play2BSplicePending) {
-				const int32 spliceFrame = _level14Play2BSpliceFrame;
-				_level14Play2BSplicePending = false;
-
-				// DOS queues L14PLY2B from the L14PLAY2 loop with startFrame
-				// equal to L14PLAY2's old timeline frame. L14PLY2B itself is the
-				// continuation clip, so the port starts it from frame 0 but uses
-				// the non-zero frame argument to preserve gameplay/video state.
-				playInteractiveVideo("LVL14/L14PLY2B.ANM", spliceFrame);
+			const char *level14Phase2Video = "LVL14/L14PLAY2.ANM";
+			bool replayingLevel14Phase2 = false;
+			while (!shouldAbortGameFlow()) {
+				_preserveInteractiveRuntimeState = replayingLevel14Phase2;
+				playInteractiveVideo(level14Phase2Video);
+				_preserveInteractiveRuntimeState = false;
 				if (shouldAbortGameFlow())
 					return false;
+
+				if (_health < 0)
+					break;
+
+				if (_level14Play2BSplicePending) {
+					const int32 spliceFrame = _level14Play2BSpliceFrame;
+					_level14Play2BSplicePending = false;
+					level14Phase2Video = "LVL14/L14PLY2B.ANM";
+
+					// DOS queues L14PLY2B from the L14PLAY2 loop with startFrame
+					// equal to L14PLAY2's old timeline frame. L14PLY2B itself is the
+					// continuation clip, so the port starts it from frame 0 but uses
+					// the non-zero frame argument to preserve gameplay/video state.
+					playInteractiveVideo(level14Phase2Video, spliceFrame);
+					if (shouldAbortGameFlow())
+						return false;
+					if (_health < 0)
+						break;
+				}
+
+				level14Phase2Complete = _level14SuccessFrames >= 0x3C;
+				if (level14Phase2Complete)
+					break;
+
+				debugC(DEBUG_INSANE, "RA1 L14 replaying phase 2: video=%s successFrames=%d targetsDestroyed=%d",
+					level14Phase2Video, (int)_level14SuccessFrames,
+					areLevel14Phase2TargetsDestroyed() ? 1 : 0);
+				replayingLevel14Phase2 = true;
 			}
+			if (shouldAbortGameFlow())
+				return false;
 		}
 
-		if (_health >= 0) {
+		if (_health >= 0 && level14Phase2Complete) {
 			playChapterCompleteCinematic("LVL14/L14END.ANM", 14, 0x69, 5,
 				nullptr, nullptr, 0, nullptr, nullptr, 0, _difficulty + 10);
 			return !shouldAbortGameFlow();
@@ -1612,8 +1677,8 @@ void InsaneRebel1::restoreInteractiveVideoAudioState() {
 void InsaneRebel1::setupInteractiveVideoState(int32 startFrame) {
 	const bool level7RouteSplice = (_currentLevel == 6 && _levelRouteIndex > 0);
 	const bool resumingRoute = startFrame > 0;
-	const bool preserveRuntimeState = resumingRoute || level7RouteSplice;
-	const bool preserveVideoState = resumingRoute && !level7RouteSplice;
+	const bool preserveRuntimeState = _preserveInteractiveRuntimeState || resumingRoute || level7RouteSplice;
+	const bool preserveVideoState = !_preserveInteractiveRuntimeState && resumingRoute && !level7RouteSplice;
 
 	SmushPlayer *splayer = _vm->_splayer;
 	_player = splayer;
@@ -1689,11 +1754,12 @@ void InsaneRebel1::resolveSeek(const char *filename, int32 startFrame, int32 &vi
 
 void InsaneRebel1::captureInteractiveVideoInput() {
 	const bool level7RouteSplice = (_currentLevel == 6 && _levelRouteIndex > 0);
+	const bool preserveInputState = _preserveInteractiveRuntimeState || level7RouteSplice;
 
 	// Center mouse, hide system cursor (we draw our own), lock mouse to window.
-	// Level 7 route splices happen inside one original gameplay loop, so keep
-	// the current input state instead of recentering between route clips.
-	if (!level7RouteSplice) {
+	// Some replays happen inside one original gameplay loop, so keep the current
+	// input state instead of recentering between route clips.
+	if (!preserveInputState) {
 		// On touchscreen devices the DOS recenter-the-cursor aiming model does not apply;
 		// warping/locking the system mouse there injects spurious motion that drifts
 		// direct touch aiming and on-screen controls.
@@ -1723,12 +1789,15 @@ void InsaneRebel1::playInteractiveVideoFile(const char *filename, int32 videoOff
 // Play interactive gameplay video (with ship physics + HUD).
 void InsaneRebel1::playInteractiveVideo(const char *filename, int32 startFrame) {
 	debugC(DEBUG_INSANE, "InsaneRebel1::playInteractiveVideo('%s', startFrame=%d)", filename, startFrame);
-	if (shouldAbortGameFlow())
+	if (shouldAbortGameFlow()) {
+		_preserveInteractiveRuntimeState = false;
 		return;
+	}
 
 	int32 videoStartFrame = 0;
 	int32 videoOffset = 0;
-	const bool preserveRuntimeState = (startFrame > 0) || (_currentLevel == 6 && _levelRouteIndex > 0);
+	const bool preserveRuntimeState = _preserveInteractiveRuntimeState ||
+		(startFrame > 0) || (_currentLevel == 6 && _levelRouteIndex > 0);
 
 	if (!preserveRuntimeState)
 		resetInteractiveVideoAudio();
@@ -1737,6 +1806,7 @@ void InsaneRebel1::playInteractiveVideo(const char *filename, int32 startFrame)
 	captureInteractiveVideoInput();
 	playInteractiveVideoFile(filename, videoOffset, videoStartFrame);
 	releaseInteractiveVideoInput();
+	_preserveInteractiveRuntimeState = false;
 }
 
 } // End of namespace Scumm


Commit: b820b7a63075dad02d735a88ac729b5cf73d130d
    https://github.com/scummvm/scummvm/commit/b820b7a63075dad02d735a88ac729b5cf73d130d
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-06-04T16:51:26+02:00

Commit Message:
SCUMM: RA1: improve shift + S to skip some sequences that have wining conditions

Changed paths:
    engines/scumm/insane/rebel1/menu.cpp
    engines/scumm/insane/rebel1/rebel.cpp
    engines/scumm/insane/rebel1/rebel.h
    engines/scumm/insane/rebel1/runlevels.cpp


diff --git a/engines/scumm/insane/rebel1/menu.cpp b/engines/scumm/insane/rebel1/menu.cpp
index 7677cf0d985..45eb5a1d6e5 100644
--- a/engines/scumm/insane/rebel1/menu.cpp
+++ b/engines/scumm/insane/rebel1/menu.cpp
@@ -895,6 +895,7 @@ bool InsaneRebel1::notifyEvent(const Common::Event &event) {
 		if (_interactiveVideoActive && !_menuActive &&
 			event.kbd.keycode == Common::KEYCODE_s &&
 			event.kbd.hasFlags(Common::KBD_SHIFT)) {
+			_interactiveVideoCheatSkipped = true;
 			_vm->_smushVideoShouldFinish = true;
 			return true;
 		}
diff --git a/engines/scumm/insane/rebel1/rebel.cpp b/engines/scumm/insane/rebel1/rebel.cpp
index 4cfee698554..4fd79f1c7eb 100644
--- a/engines/scumm/insane/rebel1/rebel.cpp
+++ b/engines/scumm/insane/rebel1/rebel.cpp
@@ -318,6 +318,7 @@ InsaneRebel1::InsaneRebel1(ScummEngine_v7 *scumm) : Insane(), _vm(scumm) {
 	_unlockAllLevels = ConfMan.getBool("rebel1_unlock_all");
 	_interactiveVideoActive = false;
 	_preserveInteractiveRuntimeState = false;
+	_interactiveVideoCheatSkipped = false;
 	_restoreInteractiveVideoAudioState = false;
 	memset(_savedInteractiveVideoTrackState, 0, sizeof(_savedInteractiveVideoTrackState));
 	memset(_savedInteractiveVideoTrackGroupId, 0, sizeof(_savedInteractiveVideoTrackGroupId));
diff --git a/engines/scumm/insane/rebel1/rebel.h b/engines/scumm/insane/rebel1/rebel.h
index 8ff100263bc..f74d6dd5d69 100644
--- a/engines/scumm/insane/rebel1/rebel.h
+++ b/engines/scumm/insane/rebel1/rebel.h
@@ -522,6 +522,7 @@ private:
 	// True only while an interactive gameplay SMUSH is running.
 	bool _interactiveVideoActive;
 	bool _preserveInteractiveRuntimeState;
+	bool _interactiveVideoCheatSkipped;
 
 	// Path branching for levels with left/right alternative videos.
 	// Original sets nextSceneA/nextSceneB when GAME 0x07 counter == 394 (0x18A).
diff --git a/engines/scumm/insane/rebel1/runlevels.cpp b/engines/scumm/insane/rebel1/runlevels.cpp
index 7f4e7ce0d41..215fe75864f 100644
--- a/engines/scumm/insane/rebel1/runlevels.cpp
+++ b/engines/scumm/insane/rebel1/runlevels.cpp
@@ -412,7 +412,7 @@ bool InsaneRebel1::runLevel1() {
 				if (_health < 0)
 					break;
 
-				if (_killCount > 4) {
+				if (_killCount > 4 || _interactiveVideoCheatSkipped) {
 					const char *pathText = _rightPathSelected ? "Path Taken: Hard" : "Path Taken: Easy";
 					const int pathBonus = _rightPathSelected ? _tuning.bonus * 3 : 0;
 					char accuracyText[80];
@@ -563,9 +563,9 @@ bool InsaneRebel1::runLevel4() {
 			if (_health < 0)
 				break;
 
-			shieldGeneratorsDestroyed =
-				_protectedTargetA == 0 && _protectedTargetB == 0 &&
-				_shieldGenHitsA > 0x30 + 0x3C && _shieldGenHitsB > 0x30;
+			shieldGeneratorsDestroyed = _interactiveVideoCheatSkipped ||
+				(_protectedTargetA == 0 && _protectedTargetB == 0 &&
+				_shieldGenHitsA > 0x30 + 0x3C && _shieldGenHitsB > 0x30);
 			if (shieldGeneratorsDestroyed)
 				break;
 
@@ -640,7 +640,7 @@ bool InsaneRebel1::runLevel5() {
 			return false;
 		}
 
-		if (_killCount <= 2) {
+		if (_killCount <= 2 && !_interactiveVideoCheatSkipped) {
 			if (_lives > 0) {
 				_lives--;
 				playCinematic("LVL5/L5RETRY.ANM");
@@ -862,6 +862,11 @@ bool InsaneRebel1::runLevel8() {
 			if (_health < 0)
 				break;
 
+			if (_interactiveVideoCheatSkipped) {
+				_walkerHealth = 0;
+				break;
+			}
+
 			if (_walkerHealth <= 0)
 				break;
 
@@ -941,6 +946,8 @@ bool InsaneRebel1::runLevel9() {
 			playInteractiveVideo(filename);
 			if (shouldAbortGameFlow() || _health < 0)
 				return -1;
+			if (_interactiveVideoCheatSkipped)
+				return (_shipPosX < kRA1CenterX) ? 0 : 1;
 			if (_killCount > 0)
 				return (_shipPosX < kRA1CenterX) ? 0 : 1;
 
@@ -1011,7 +1018,7 @@ bool InsaneRebel1::runLevel9() {
 			if (_health < 0)
 				break;
 
-			if (_killCount < 15) {
+			if (_killCount < 15 && !_interactiveVideoCheatSkipped) {
 				playInteractiveVideo("LVL9/L9PLAY3B.ANM");
 				if (shouldAbortGameFlow())
 					return false;
@@ -1164,7 +1171,7 @@ bool InsaneRebel1::runLevel11() {
 				break;
 
 			// Original: killCount > 4 means pass
-			if (_killCount > 4)
+			if (_killCount > 4 || _interactiveVideoCheatSkipped)
 				break;
 
 			// Not enough kills — retry
@@ -1223,7 +1230,7 @@ bool InsaneRebel1::runLevel12() {
 			if (shouldAbortGameFlow())
 				return false;
 
-			if (_levelGameplayPhase == 1) {
+			if (_levelGameplayPhase == 1 && !_interactiveVideoCheatSkipped) {
 				playCinematic("LVL12/L12RETRY.ANM");
 				if (shouldAbortGameFlow())
 					return false;
@@ -1324,7 +1331,7 @@ bool InsaneRebel1::runLevel14() {
 			if (_health < 0)
 				break;
 
-			level14Phase1Complete = _level14SuccessFrames >= 0x3C;
+			level14Phase1Complete = _interactiveVideoCheatSkipped || _level14SuccessFrames >= 0x3C;
 			if (level14Phase1Complete)
 				break;
 
@@ -1362,6 +1369,11 @@ bool InsaneRebel1::runLevel14() {
 				if (_health < 0)
 					break;
 
+				if (_interactiveVideoCheatSkipped) {
+					level14Phase2Complete = true;
+					break;
+				}
+
 				if (_level14Play2BSplicePending) {
 					const int32 spliceFrame = _level14Play2BSpliceFrame;
 					_level14Play2BSplicePending = false;
@@ -1378,7 +1390,7 @@ bool InsaneRebel1::runLevel14() {
 						break;
 				}
 
-				level14Phase2Complete = _level14SuccessFrames >= 0x3C;
+				level14Phase2Complete = _interactiveVideoCheatSkipped || _level14SuccessFrames >= 0x3C;
 				if (level14Phase2Complete)
 					break;
 
@@ -1454,7 +1466,7 @@ bool InsaneRebel1::runLevel15() {
 				return false;
 		}
 
-		if (_health >= 0 && !_torpedoFired) {
+		if (_health >= 0 && !_torpedoFired && !_interactiveVideoCheatSkipped) {
 			debugC(DEBUG_INSANE, "InsaneRebel1::runLevel15: Level 15 torpedo run ended without exhaust-port hit");
 			return false;
 		}
@@ -1791,9 +1803,11 @@ void InsaneRebel1::playInteractiveVideo(const char *filename, int32 startFrame)
 	debugC(DEBUG_INSANE, "InsaneRebel1::playInteractiveVideo('%s', startFrame=%d)", filename, startFrame);
 	if (shouldAbortGameFlow()) {
 		_preserveInteractiveRuntimeState = false;
+		_interactiveVideoCheatSkipped = false;
 		return;
 	}
 
+	_interactiveVideoCheatSkipped = false;
 	int32 videoStartFrame = 0;
 	int32 videoOffset = 0;
 	const bool preserveRuntimeState = _preserveInteractiveRuntimeState ||


Commit: d9fc895bf5e6e9c43ae00274ae88053d317af2d4
    https://github.com/scummvm/scummvm/commit/d9fc895bf5e6e9c43ae00274ae88053d317af2d4
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-06-04T17:11:24+02:00

Commit Message:
SCUMM: RA1: added missing overlay in level 5

Changed paths:
    engines/scumm/insane/rebel1/rebel.h
    engines/scumm/insane/rebel1/render.cpp
    engines/scumm/smush/rebel/smush_player_ra1.cpp


diff --git a/engines/scumm/insane/rebel1/rebel.h b/engines/scumm/insane/rebel1/rebel.h
index f74d6dd5d69..26dc27c6014 100644
--- a/engines/scumm/insane/rebel1/rebel.h
+++ b/engines/scumm/insane/rebel1/rebel.h
@@ -111,6 +111,7 @@ public:
 	bool isTouchscreenActive() const;
 	void setFrameHasGameChunk(bool hasGameChunk) { _frameHasGameChunk = hasGameChunk; }
 	int getCurrentLevel() const { return _currentLevel; }
+	int getLevelGameplayPhase() const { return _levelGameplayPhase; }
 	uint16 getActiveGameOpcode() const { return _activeGameOpcode; }
 	uint16 getEffectiveGameOpcode() const;
 	bool hasFrameGameOpcode(uint16 opcode) const {
@@ -245,6 +246,7 @@ private:
 		bool drawTargetBoxes);
 	void handleLevel14Play2BSplice(int32 curFrame, int32 maxFrame);
 	void renderLevel7RouteOverlays(byte *dst, int pitch, int width, int height);
+	void renderLevel5Part2Overlay(byte *dst, int pitch, int width, int height, int32 curFrame);
 	void renderLevel11HitsOverlay(byte *dst, int pitch, int width, int height);
 	void resetEnemyShotSlots();
 	void renderLevel13EnemyShots(byte *dst, int pitch, int width, int height);
diff --git a/engines/scumm/insane/rebel1/render.cpp b/engines/scumm/insane/rebel1/render.cpp
index f428e495c39..1a17b10eb9f 100644
--- a/engines/scumm/insane/rebel1/render.cpp
+++ b/engines/scumm/insane/rebel1/render.cpp
@@ -797,6 +797,9 @@ void InsaneRebel1::procPostRendering(byte *renderBitmap, int32 codecparam, int32
 	renderExplosions(renderBitmap, pitch, width, height);
 	handleLevel14Play2BSplice(curFrame, maxFrame);
 
+	if (_currentLevel == 4 && _levelGameplayPhase == 2)
+		renderLevel5Part2Overlay(renderBitmap, pitch, width, height, curFrame);
+
 	if (_currentLevel == 10)
 		renderLevel11HitsOverlay(renderBitmap, pitch, width, height);
 
@@ -1026,6 +1029,35 @@ void InsaneRebel1::renderLevel11HitsOverlay(byte *dst, int pitch, int width, int
 	drawFontBankString(dst, pitch, width, height, 0x119, 0x16, hitsStr);
 }
 
+// renderLevel5Part2Overlay — RunLevel5Flow (0x176D0-0x1777E) draws the
+// part-2 instruction reveal, then switches to the live target count.
+void InsaneRebel1::renderLevel5Part2Overlay(byte *dst, int pitch, int width, int height, int32 curFrame) {
+	if (_hudFontBank.numSprites <= 0 && _techFontBank.numSprites <= 0)
+		return;
+
+	const int viewportX = _player ? ra1Player()->_ra1ViewportOffsetX : 0;
+	const int viewportY = _player ? ra1Player()->_ra1ViewportOffsetY : 0;
+	int16 overlayX = 0x88;
+	int16 overlayY = 0x8C;
+	projectGameplayPoint(overlayX, overlayY);
+	overlayX = (int16)(0x88 - ((overlayX - 0x88) >> 2));
+	overlayY = (int16)(0x8C - ((overlayY - 0x8C) >> 2));
+
+	if (curFrame < 0x32) {
+		const char instructionText[] = "<<SHOOT TARGETS FOR BONUS";
+		const int revealChars = CLIP<int>((int)curFrame, 0, (int)sizeof(instructionText) - 1);
+		char revealedText[sizeof(instructionText)];
+		Common::strlcpy(revealedText, instructionText, revealChars + 1);
+		drawFontBankString(dst, pitch, width, height,
+			viewportX + overlayX - 0x27, viewportY + overlayY, revealedText);
+	} else {
+		char hitsStr[16];
+		Common::sprintf_s(hitsStr, "<<HITS: %d", (int)_killCount);
+		drawFontBankString(dst, pitch, width, height,
+			viewportX + overlayX, viewportY + overlayY, hitsStr);
+	}
+}
+
 void InsaneRebel1::resetEnemyShotSlots() {
 	memset(_enemyShotSlots, 0, sizeof(_enemyShotSlots));
 }
diff --git a/engines/scumm/smush/rebel/smush_player_ra1.cpp b/engines/scumm/smush/rebel/smush_player_ra1.cpp
index 880eb987be9..19add73b0d5 100644
--- a/engines/scumm/smush/rebel/smush_player_ra1.cpp
+++ b/engines/scumm/smush/rebel/smush_player_ra1.cpp
@@ -241,13 +241,16 @@ bool SmushPlayerRebel1::handleGameFetch(int32 subSize, Common::SeekableReadStrea
 			if (rebel1->isInteractiveVideoActive()) {
 				const uint16 gameOp = rebel1->getActiveGameOpcode();
 				const bool fullWidthStoredPatch = (_storedFobjWidth == _vm->_screenWidth);
-				const bool level8WalkerPatch = (gameOp == 0x0B && rebel1->getCurrentLevel() == 7);
+				const bool projectedCockpitPatch = (gameOp == 0x0B &&
+					((rebel1->getCurrentLevel() == 4 && rebel1->getLevelGameplayPhase() == 2) ||
+						rebel1->getCurrentLevel() == 7));
 				// Keep the direct viewport placement used by the existing 0x0B
-				// compatibility path, except for Level 8's walker cockpit patch.
+				// compatibility path, except for the Level 5 part 2 and Level 8
+				// cockpit patches.
 				// DOS FTCH goes through FUN_28D0A, which sets DispatchFobjCodec
-				// flag 0x800; Level 8 depends on that quarter-projection so the
-				// cockpit and RunLevel8Flow indicators move together.
-				if (fullWidthStoredPatch || (gameOp == 0x0B && !level8WalkerPatch) ||
+				// flag 0x800; these cockpit patches depend on that quarter-projection
+				// so the cockpit and flow-script indicators move together.
+				if (fullWidthStoredPatch || (gameOp == 0x0B && !projectedCockpitPatch) ||
 						gameOp == 0x19 || gameOp == 0x1A) {
 					left += _ra1ViewportOffsetX;
 					top += _ra1ViewportOffsetY;




More information about the Scummvm-git-logs mailing list