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

neuromancer noreply at scummvm.org
Wed Jun 3 17:38:05 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:
4de0e53e91 SCUMM: RA2: improve rendering of level 12
d2be31c84a SCUMM: RA2: make level 12 progress until the end
5905f3934c SCUMM: RA2: proper shooting in level 12
3f6b1ed8e4 SCUMM: RA2: render overlay in level 12
e0985e843e SCUMM: RA2: removed some useless statics


Commit: 4de0e53e915325440cccd4fe3179ab313c3b3e78
    https://github.com/scummvm/scummvm/commit/4de0e53e915325440cccd4fe3179ab313c3b3e78
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-06-03T16:59:37+02:00

Commit Message:
SCUMM: RA2: improve rendering of level 12

Changed paths:
    engines/scumm/insane/rebel2/iact.cpp
    engines/scumm/insane/rebel2/render.cpp
    engines/scumm/smush/rebel/smush_player_ra2.cpp
    engines/scumm/smush/rebel/smush_player_ra2.h


diff --git a/engines/scumm/insane/rebel2/iact.cpp b/engines/scumm/insane/rebel2/iact.cpp
index c7d1bf5ca9f..906160a537e 100644
--- a/engines/scumm/insane/rebel2/iact.cpp
+++ b/engines/scumm/insane/rebel2/iact.cpp
@@ -86,7 +86,7 @@ void InsaneRebel2::procPreRendering(byte *renderBitmap) {
 	// This is called at the start of handleFrame(), before any FOBJ chunks are processed.
 	//
 	// IMPORTANT: Only restore when the render buffer pitch matches the background pitch (320).
-	// Levels like Level 12 (Sewers) use oversized buffers (640x260) where FOBJ/FETCH handles
+	// Levels like Level 12 (Sewers) use oversized buffers (424x260) where FOBJ/FETCH handles
 	// background restoration. Copying the 320-wide background into a 640-wide buffer with
 	// hardcoded pitch=320 would corrupt the corridor rendering.
 	if (_rebelHandler == 8 && _level2BackgroundLoaded && _level2Background && renderBitmap) {
diff --git a/engines/scumm/insane/rebel2/render.cpp b/engines/scumm/insane/rebel2/render.cpp
index db4780badeb..afabee2e76a 100644
--- a/engines/scumm/insane/rebel2/render.cpp
+++ b/engines/scumm/insane/rebel2/render.cpp
@@ -3340,7 +3340,7 @@ void InsaneRebel2::renderHandler25ShipPre(byte *renderBitmap, int pitch, int wid
 		return;
 
 	// CRITICAL: Clip height to 180 (0xb4) + viewport Y to avoid drawing over status bar.
-	// For oversized buffers (e.g., Level 12's 640x260), the status bar is at
+	// For oversized buffers (e.g., Level 12's 424x260), the status bar is at
 	// Y = 180 + _viewY in buffer coordinates.
 	int renderHeight = MIN(height, 180 + _viewY);
 
@@ -3378,7 +3378,7 @@ void InsaneRebel2::renderHandler25ShipPre(byte *renderBitmap, int pitch, int wid
 
 		// Add viewport offset so sprite follows the visible area.
 		// For 320x200 buffers (Level 2), _viewX/_viewY are 0 — no change.
-		// For oversized buffers (Level 12's 640x260), the viewport scrolls
+		// For oversized buffers (Level 12's 424x260), the viewport scrolls
 		// and sprites must be drawn at the correct position within it.
 		int drawX = _rebelViewOffset2X + spriteXOffset + _viewX;
 		int drawY = _rebelViewOffset2Y + spriteYOffset + _viewY;
diff --git a/engines/scumm/smush/rebel/smush_player_ra2.cpp b/engines/scumm/smush/rebel/smush_player_ra2.cpp
index 64f483df637..7389e22422c 100644
--- a/engines/scumm/smush/rebel/smush_player_ra2.cpp
+++ b/engines/scumm/smush/rebel/smush_player_ra2.cpp
@@ -41,6 +41,10 @@
 
 namespace Scumm {
 
+bool isRebel2FullFrameDeltaCodec(int codec) {
+	return codec == SMUSH_CODEC_DELTA_BLOCKS || codec == SMUSH_CODEC_DELTA_GLYPHS;
+}
+
 // ---------------------------------------------------------------------------
 // SmushPlayerRebel2 — construction / destruction
 // ---------------------------------------------------------------------------
@@ -687,17 +691,16 @@ void SmushPlayerRebel2::ra2PrepareFrameObjectSurface(int left, int top, int widt
 		_ra2FrameObjectSurfaceHeight = (int)bottom;
 }
 
-bool SmushPlayerRebel2::ra2SelectFrameBuffer(int width, int height) {
+bool SmushPlayerRebel2::ra2SelectFrameBuffer(int codec, int width, int height) {
 	// Rebel2 uses a special buffer for all non-matching frames.
-	// Oversized FOBJ chunks are clipped against the original 424x260 surface.
-	// Level 1: First frame is 424x260 (background), small sprites reuse same buffer
-	// Level 2: Uses virtual screen directly (handled below when _specialBuffer stays null)
+	// Oversized full-frame delta codecs decode into a tightly packed surface
+	// with their own width as pitch. Other codecs honor left/top and pitch.
 	const int screenSize = _vm->_screenWidth * _vm->_screenHeight;
 	const int64 fobjSize64 = (int64)width * height;
 	int surfaceWidth = width;
 	int surfaceHeight = height;
 
-	if (fobjSize64 > screenSize) {
+	if (fobjSize64 > screenSize && !isRebel2FullFrameDeltaCodec(codec)) {
 		surfaceWidth = MAX(surfaceWidth, _ra2FrameObjectSurfaceWidth);
 		surfaceHeight = MAX(surfaceHeight, _ra2FrameObjectSurfaceHeight);
 	}
@@ -721,13 +724,13 @@ bool SmushPlayerRebel2::ra2SelectFrameBuffer(int width, int height) {
 			free(_specialBuffer);
 			_specialBuffer = newSpecialBuffer;
 			_specialBufferSize = bufSize;
-			_width = surfaceWidth;
-			_height = surfaceHeight;
 			// Zero-fill the new buffer to avoid garbage in areas not written by FOBJ codec
 			memset(_specialBuffer, 0, bufSize);
 			debugC(DEBUG_SMUSH, "SmushPlayerRebel2::ra2SelectFrameBuffer: Allocated new _specialBuffer %dx%d for FOBJ %dx%d (%d bytes)",
 				surfaceWidth, surfaceHeight, width, height, bufSize);
 		}
+		_width = surfaceWidth;
+		_height = surfaceHeight;
 	}
 
 	if (bufSize > screenSize &&
@@ -859,7 +862,7 @@ void SmushPlayerRebel2::handleGameParseNextFrame() {
 
 bool SmushPlayerRebel2::handleGameFrameBufferSelect(int codec, int width, int height) {
 	if ((height != _vm->_screenHeight) || (width != _vm->_screenWidth)) {
-		return ra2SelectFrameBuffer(width, height);
+		return ra2SelectFrameBuffer(codec, width, height);
 	}
 	return false;
 }
diff --git a/engines/scumm/smush/rebel/smush_player_ra2.h b/engines/scumm/smush/rebel/smush_player_ra2.h
index 5f4a33dbae8..20c20c55a29 100644
--- a/engines/scumm/smush/rebel/smush_player_ra2.h
+++ b/engines/scumm/smush/rebel/smush_player_ra2.h
@@ -68,7 +68,7 @@ private:
 							   int pos_x, int pos_y, int left, int top,
 							   int width, int height, TextStyleFlags flg);
 	void ra2PrepareFrameObjectSurface(int left, int top, int width, int height);
-	bool ra2SelectFrameBuffer(int width, int height);
+	bool ra2SelectFrameBuffer(int codec, int width, int height);
 	bool ra2DecodeCodec(int codec, const uint8 *src, int left, int top,
 						int width, int height, int pitch, int dataSize);
 	void ra2HandleDeltaPalette(int32 subSize, Common::SeekableReadStream &b);


Commit: d2be31c84aeb11e949ed8ce7c71e4ec06bb78d6d
    https://github.com/scummvm/scummvm/commit/d2be31c84aeb11e949ed8ce7c71e4ec06bb78d6d
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-06-03T17:39:38+02:00

Commit Message:
SCUMM: RA2: make level 12 progress until the end

Changed paths:
    engines/scumm/insane/rebel2/iact.cpp
    engines/scumm/insane/rebel2/rebel.h
    engines/scumm/smush/rebel/smush_player_ra2.cpp
    engines/scumm/smush/rebel/smush_player_ra2.h


diff --git a/engines/scumm/insane/rebel2/iact.cpp b/engines/scumm/insane/rebel2/iact.cpp
index 906160a537e..15e1a040343 100644
--- a/engines/scumm/insane/rebel2/iact.cpp
+++ b/engines/scumm/insane/rebel2/iact.cpp
@@ -602,22 +602,30 @@ void InsaneRebel2::updateOpcode6Handler(int16 par2) {
 }
 
 // ScummVM refactor helper for opcode 6 Handler 8, not a separate retail function.
-void InsaneRebel2::handleOpcode6Handler8(int16 par3, int16 par4) {
+void InsaneRebel2::handleOpcode6Handler8(Common::SeekableReadStream &b, int16 par4) {
 	// Handler 8 specific logic (third-person on foot) - FUN_00401234 case 4.
-	// Set ship level mode (DAT_0043e000 = par3)
-	_shipLevelMode = par3;
+	// DAT_0043e000 = local_14[3], which maps to the IACT header's par4/userId.
+	_shipLevelMode = par4;
 
-	// If par4 == 1, enable status bar and re-render laser texture (FUN_0040bb87)
-	if (par4 == 1) {
+	// local_14[4] is the first body word after the 8-byte IACT header.
+	int16 bodyStatusFlag = 0;
+	if (b.pos() + 2 <= b.size()) {
+		int64 savedPos = b.pos();
+		bodyStatusFlag = b.readSint16LE();
+		b.seek(savedPos);
+	}
+
+	// If local_14[4] == 1, enable status bar and re-render laser texture (FUN_0040bb87)
+	if (bodyStatusFlag == 1) {
 		_rebelStatusBarSprite = 5;
 		if (_smush_iconsNut && _smush_iconsNut->getNumChars() > 5) {
 			initLaserTexture(_smush_iconsNut, 5);
 		}
 	}
 
-	// Reset state when shipLevelMode != 0 && par4 == 1 (FUN_401234 lines 97-103)
+	// Reset state when shipLevelMode != 0 && local_14[4] == 1 (FUN_00401234 lines 97-103)
 	// Guard with _rebelOp6Initialized: runs once per wave video, not per frame.
-	if (_shipLevelMode != 0 && par4 == 1 && !_rebelOp6Initialized) {
+	if (_shipLevelMode != 0 && bodyStatusFlag == 1 && !_rebelOp6Initialized) {
 		clearBit(0);
 		for (int i = 0; i < 512; i++) {
 			_rebelLinks[i][0] = 0;
@@ -758,8 +766,8 @@ void InsaneRebel2::handleOpcode6Handler8(int16 par3, int16 par4) {
 			_vm->getActionState(kScummActionInsaneAttack);
 	}
 
-	debug("Rebel2 Opcode 6 (Handler 8): mode=%d range=%d shipPos=(%d,%d) target=(%d,%d) firing=%d dir=(%d,%d,%d)",
-		_shipLevelMode, _movementRangeLimit, _shipPosX, _shipPosY, _shipTargetX, _shipTargetY, _shipFiring,
+	debug("Rebel2 Opcode 6 (Handler 8): mode=%d bodyFlag=%d range=%d shipPos=(%d,%d) target=(%d,%d) firing=%d dir=(%d,%d,%d)",
+		_shipLevelMode, bodyStatusFlag, _movementRangeLimit, _shipPosX, _shipPosY, _shipTargetX, _shipTargetY, _shipFiring,
 		_shipDirectionH, _shipDirectionV, _shipDirectionIndex);
 }
 
@@ -1465,20 +1473,20 @@ void InsaneRebel2::scanOpcode6EmbeddedAnim(byte *renderBitmap, Common::SeekableR
 // iactRebel2Opcode6 -- Level setup / mode switch (FUN_41CADB case 4)
 //
 // Per-wave initialization: clears bit table, resets link tables, configures
-// handler mode (ship/turret/corridor), and loads collision zones. Called once
-// per wave video on frame 0.
+// handler mode (ship/turret/corridor), and loads collision zones. The original
+// gates the reset with handler-specific IACT fields, not always frame 0.
 //
 void InsaneRebel2::iactRebel2Opcode6(byte *renderBitmap, Common::SeekableReadStream &b, int32 chunkSize, int16 par2, int16 par3, int16 par4) {
 	// Opcode 6: Level setup / mode switch
 	// Based on FUN_41CADB case 4 (switch on *local_14 - 2 == 4, meaning opcode 6)
 	//
 	// For Handler 8 (third-person on foot) - FUN_00401234 case 4:
-	// - par3 sets ship level mode (DAT_0043e000)
-	// - par4 == 1 triggers status bar display and state reset
+	// - par4 sets ship level mode (DAT_0043e000)
+	// - first body word == 1 triggers status bar display and state reset
 	// - Updates ship position based on mouse input
 	//
 	// For Handler 0x26/0x19 (turret/FPS):
-	// - Same par4 == 1 behavior
+	// - Handler-specific status/reset word
 	// - Different view offset calculations
 
 	debug("Rebel2 IACT Opcode 6: par2=%d par3=%d par4=%d", par2, par3, par4);
@@ -1486,7 +1494,7 @@ void InsaneRebel2::iactRebel2Opcode6(byte *renderBitmap, Common::SeekableReadStr
 	updateOpcode6Handler(par2);
 
 	if (_rebelHandler == 8) {
-		handleOpcode6Handler8(par3, par4);
+		handleOpcode6Handler8(b, par4);
 		return;
 	}
 
diff --git a/engines/scumm/insane/rebel2/rebel.h b/engines/scumm/insane/rebel2/rebel.h
index f2d5cdb3f78..8dc6ceb75d0 100644
--- a/engines/scumm/insane/rebel2/rebel.h
+++ b/engines/scumm/insane/rebel2/rebel.h
@@ -538,7 +538,7 @@ public:
 	void iactRebel2Opcode3(Common::SeekableReadStream &b, int16 par2, int16 par3, int16 par4);
 	void iactRebel2Opcode6(byte *renderBitmap, Common::SeekableReadStream &b, int32 chunkSize, int16 par2, int16 par3, int16 par4);
 	void updateOpcode6Handler(int16 par2);
-	void handleOpcode6Handler8(int16 par3, int16 par4);
+	void handleOpcode6Handler8(Common::SeekableReadStream &b, int16 par4);
 	void handleOpcode6Handler7(Common::SeekableReadStream &b, int16 par4);
 	void handleOpcode6Handler25(byte *renderBitmap, Common::SeekableReadStream &b, int16 par2, int16 par3, int16 par4);
 	void handleOpcode6Turret(Common::SeekableReadStream &b, int16 par4);
@@ -1063,7 +1063,7 @@ public:
 	int16 _shipTargetY;              // DAT_0043e004 - Target Y
 
 	// Level mode for handler 8 (different from _rebelLevelType)
-	// Set by opcode 6 par3, affects ship rendering behavior
+	// Set by opcode 6 par4, affects ship rendering behavior
 	// Mode 0/1/3: "Shooting" - full movement range (127)
 	// Mode 2: "Covered" - restricted movement (41) - behind cover
 	// Mode 4: "Autopilot" - no shooting, scripted movement
diff --git a/engines/scumm/smush/rebel/smush_player_ra2.cpp b/engines/scumm/smush/rebel/smush_player_ra2.cpp
index 7389e22422c..3b0d660c7fb 100644
--- a/engines/scumm/smush/rebel/smush_player_ra2.cpp
+++ b/engines/scumm/smush/rebel/smush_player_ra2.cpp
@@ -41,6 +41,9 @@
 
 namespace Scumm {
 
+// FUN_00423880 is initialized at startup with a 400000-byte preload buffer.
+constexpr int kRebel2LoadBufferSize = 400000;
+
 bool isRebel2FullFrameDeltaCodec(int codec) {
 	return codec == SMUSH_CODEC_DELTA_BLOCKS || codec == SMUSH_CODEC_DELTA_GLYPHS;
 }
@@ -89,7 +92,7 @@ void SmushPlayerRebel2::initGamePlayerFields() {
 	_loadBufferOffset = 0;
 	_loadReadOffset = 8;  // Original starts reading at offset 8 (skips header)
 	_lastLoadChunkIdx = -1;
-	_totalLoadChunks = 0;
+	_loadStreamId = 0;
 	_ra2FrameSourceSkipY = 0;
 	_ra2FrameObjectSurfaceWidth = 0;
 	_ra2FrameObjectSurfaceHeight = 0;
@@ -141,19 +144,16 @@ void SmushPlayerRebel2::initGameVideoState() {
 
 /**
  * RA2-specific cleanup in SmushPlayer::release().
- * Frees stored FOBJ data but preserves _frameBuffer across videos.
+ * Preserves the stored FOBJ and _frameBuffer across videos.
  */
 void SmushPlayerRebel2::releaseGameVideoState() {
-	free(_storedFobjData);
-	_storedFobjData = nullptr;
-	_storedFobjDataSize = 0;
-	_storedFobjParm2 = 0;
 	free(_lastFobjData);
 	_lastFobjData = nullptr;
 	_lastFobjDataSize = 0;
 	_hasFrameFobjForGost = false;
-	// Preserve _frameBuffer across videos so that gameplay videos (which have no
-	// background FOBJ) can use the stored background from the previous BEG video.
+	// FUN_00423880 allocates the STOR overlay buffer once for the SMUSH subsystem,
+	// and FUN_004246d0 replays the last stored FOBJ even after a new SAN starts.
+	// Level 12 wave segments depend on this when 12P01_B.SAN starts with FTCH.
 }
 
 /**
@@ -585,7 +585,7 @@ void SmushPlayerRebel2::handleGameLoad(int32 subSize, Common::SeekableReadStream
  * Handle LOAD chunk for Rebel Assault 2.
  *
  * LOAD chunks stream embedded resource data across multiple frames.
- * The data is accumulated in a buffer and consumed by the audio system.
+ * FUN_00424450 treats word 0 as the stream id and word 1 as the chunk index.
  */
 void SmushPlayerRebel2::handleLoad(int32 subSize, Common::SeekableReadStream &b) {
 	debugC(DEBUG_SMUSH, "SmushPlayerRebel2::handleLoad()");
@@ -595,26 +595,24 @@ void SmushPlayerRebel2::handleLoad(int32 subSize, Common::SeekableReadStream &b)
 		return;
 	}
 
-	int16 totalChunks = b.readUint16LE();
+	int16 streamId = b.readUint16LE();
 	int16 chunkIndex = b.readUint16LE();
 	b.skip(6);  // Unknown/padding
 
 	int32 dataSize = subSize - 10;
 
-	debugC(DEBUG_SMUSH, "SmushPlayerRebel2::handleLoad: chunk %d/%d, dataSize=%d, bufferOffset=%d",
-		chunkIndex, totalChunks, dataSize, _loadBufferOffset);
+	debugC(DEBUG_SMUSH, "SmushPlayerRebel2::handleLoad: stream=%d chunk=%d dataSize=%d bufferOffset=%d",
+		streamId, chunkIndex, dataSize, _loadBufferOffset);
 
-	// First chunk in sequence - reset buffer state
 	if (chunkIndex == 0) {
 		_loadBufferOffset = 0;
 		_loadReadOffset = 8;
 		_lastLoadChunkIdx = -1;
-		_totalLoadChunks = totalChunks;
+		_loadStreamId = streamId;
 
-		int32 estimatedSize = totalChunks * 600;
-		if (_loadBuffer == nullptr || _loadBufferSize < estimatedSize) {
+		if (_loadBuffer == nullptr || _loadBufferSize < kRebel2LoadBufferSize) {
 			free(_loadBuffer);
-			_loadBufferSize = estimatedSize;
+			_loadBufferSize = kRebel2LoadBufferSize;
 			_loadBuffer = (byte *)malloc(_loadBufferSize);
 			if (_loadBuffer == nullptr) {
 				warning("SmushPlayerRebel2::handleLoad: Failed to allocate %d bytes for LOAD buffer",
@@ -627,31 +625,23 @@ void SmushPlayerRebel2::handleLoad(int32 subSize, Common::SeekableReadStream &b)
 		}
 	}
 
-	// Check sequential order
 	if (_lastLoadChunkIdx + 1 != chunkIndex) {
 		debugC(DEBUG_SMUSH, "SmushPlayerRebel2::handleLoad: Non-sequential chunk %d (expected %d), skipping",
 			chunkIndex, _lastLoadChunkIdx + 1);
 		return;
 	}
 
-	// Check buffer capacity
-	if (_loadBuffer == nullptr || _loadBufferOffset + dataSize >= _loadBufferSize) {
+	if (_loadBuffer == nullptr || _loadBufferOffset + subSize >= _loadBufferSize) {
 		warning("SmushPlayerRebel2::handleLoad: Buffer overflow - offset=%d size=%d limit=%d",
-			_loadBufferOffset, dataSize, _loadBufferSize);
+			_loadBufferOffset, subSize, _loadBufferSize);
 		return;
 	}
 
-	// Copy data to buffer
 	b.read(_loadBuffer + _loadBufferOffset, dataSize);
 	_loadBufferOffset += dataSize;
 	_lastLoadChunkIdx = chunkIndex;
 
 	debugC(DEBUG_SMUSH, "SmushPlayerRebel2::handleLoad: Accumulated %d bytes total", _loadBufferOffset);
-
-	if (chunkIndex == totalChunks - 1) {
-		debugC(DEBUG_SMUSH, "SmushPlayerRebel2::handleLoad: Sequence complete - %d chunks, %d bytes total",
-			totalChunks, _loadBufferOffset);
-	}
 }
 
 /**
diff --git a/engines/scumm/smush/rebel/smush_player_ra2.h b/engines/scumm/smush/rebel/smush_player_ra2.h
index 20c20c55a29..35af253e0f6 100644
--- a/engines/scumm/smush/rebel/smush_player_ra2.h
+++ b/engines/scumm/smush/rebel/smush_player_ra2.h
@@ -82,7 +82,7 @@ private:
 	int32 _loadBufferOffset;
 	int32 _loadReadOffset;
 	int16 _lastLoadChunkIdx;
-	int16 _totalLoadChunks;
+	int16 _loadStreamId;
 	int _ra2FrameSourceSkipY;
 	int _ra2FrameObjectSurfaceWidth;
 	int _ra2FrameObjectSurfaceHeight;


Commit: 5905f3934ccee38d0a2d1348619e51c55deefece
    https://github.com/scummvm/scummvm/commit/5905f3934ccee38d0a2d1348619e51c55deefece
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-06-03T17:58:24+02:00

Commit Message:
SCUMM: RA2: proper shooting in level 12

Changed paths:
    engines/scumm/insane/rebel2/iact.cpp
    engines/scumm/insane/rebel2/rebel.cpp
    engines/scumm/insane/rebel2/rebel.h
    engines/scumm/insane/rebel2/render.cpp


diff --git a/engines/scumm/insane/rebel2/iact.cpp b/engines/scumm/insane/rebel2/iact.cpp
index 15e1a040343..60c194e6a0d 100644
--- a/engines/scumm/insane/rebel2/iact.cpp
+++ b/engines/scumm/insane/rebel2/iact.cpp
@@ -2151,8 +2151,8 @@ bool InsaneRebel2::loadHandler8ShipSprites(byte *animData, int32 size, int16 par
 	// par4 values (from IACT data offset +6, NOT par3 which is always 0):
 	//   1: POV001 - Primary ship sprite (DAT_0047e010 / _shipSprite)
 	//   3: POV004 - Secondary ship sprite (DAT_0047e028 / _shipSprite2)
-	//   6: POV002 - Ship overlay 1 (DAT_0047e020 / _shipOverlay1)
-	//   7: POV003 - Ship overlay 2 (DAT_0047e018 / _shipOverlay2)
+	//   6: POV002 - Shot impact overlay (DAT_0047e020 / _shipOverlay1)
+	//   7: POV003 - Shot impact overlay (DAT_0047e018 / _shipOverlay2)
 
 	if (!animData || size <= 0) {
 		return false;
@@ -2183,11 +2183,11 @@ bool InsaneRebel2::loadHandler8ShipSprites(byte *animData, int32 size, int16 par
 		delete _shipSprite2;
 		_shipSprite2 = newNut;
 		break;
-	case 6:  // POV002 - Ship overlay 1
+	case 6:  // POV002 - Shot impact overlay
 		delete _shipOverlay1;
 		_shipOverlay1 = newNut;
 		break;
-	case 7:  // POV003 - Ship overlay 2
+	case 7:  // POV003 - Shot impact overlay
 		delete _shipOverlay2;
 		_shipOverlay2 = newNut;
 		break;
diff --git a/engines/scumm/insane/rebel2/rebel.cpp b/engines/scumm/insane/rebel2/rebel.cpp
index a777dc8353e..a3acf396b74 100644
--- a/engines/scumm/insane/rebel2/rebel.cpp
+++ b/engines/scumm/insane/rebel2/rebel.cpp
@@ -347,6 +347,13 @@ InsaneRebel2::InsaneRebel2(ScummEngine_v7 *scumm) {
 		_vehicleShots[i].targetX = 0;
 		_vehicleShots[i].targetY = 0;
 	}
+	for (i = 0; i < 7; i++) {
+		_vehicleShotImpacts[i].counter = 0;
+		_vehicleShotImpacts[i].x = 0;
+		_vehicleShotImpacts[i].y = 0;
+		_vehicleShotImpacts[i].spriteIndex = 0;
+	}
+	_vehicleShotImpactIndex = 0;
 
 	// Initialize Handler 7 Space shot system (FUN_40FADF)
 	for (i = 0; i < 2; i++) {
@@ -1501,10 +1508,25 @@ int32 InsaneRebel2::processMouse() {
 	// - Handler 25 keeps edge-triggered clicks due cover-toggle/sticky input semantics.
 	// - Other gameplay handlers fire while button is held; slot counters still rate-limit.
 	bool triggerShot = (_rebelHandler == 25) ? (leftPressed && !leftWasPressed) : leftPressed;
-	if (triggerShot && isShootingAllowed()) {
-		Common::Point mousePos = (_rebelHandler == 7) ? getHandler7ShotTargetPoint() : getGameplayAimPoint();
-		debug("Rebel2 Click: Mouse=(%d,%d) Enemies=%d",
-			mousePos.x, mousePos.y, _enemies.size());
+	bool canShoot = isShootingAllowed();
+	if (_rebelHandler == 8) {
+		// FUN_00401CCF uses the same per-frame fire bit both to spawn shots and
+		// to choose the POV gun sprite. Keep the sprite driven by the event-manager
+		// input that processMouse() uses during SMUSH playback.
+		_shipFiring = triggerShot && canShoot;
+	}
+	if (triggerShot && canShoot) {
+		Common::Point mousePos;
+		if (_rebelHandler == 7) {
+			mousePos = getHandler7ShotTargetPoint();
+		} else if (_rebelHandler == 8) {
+			mousePos = getHandler8ShotTargetPoint();
+		} else {
+			mousePos = getGameplayAimPoint();
+		}
+		Common::Point gameplayAim = getGameplayAimPoint();
+		debug("Rebel2 Click: Mouse=(%d,%d) Target=(%d,%d) Enemies=%d",
+			gameplayAim.x, gameplayAim.y, mousePos.x, mousePos.y, _enemies.size());
 
 		// Spawn visual shot immediately
 		spawnShot(mousePos.x, mousePos.y);
diff --git a/engines/scumm/insane/rebel2/rebel.h b/engines/scumm/insane/rebel2/rebel.h
index 8dc6ceb75d0..54489204056 100644
--- a/engines/scumm/insane/rebel2/rebel.h
+++ b/engines/scumm/insane/rebel2/rebel.h
@@ -596,6 +596,7 @@ public:
 
 	// Draw Handler 8 ship sprite (third-person on foot - POV sprites)
 	void renderHandler8Ship(byte *renderBitmap, int pitch, int width, int height);
+	void renderVehicleShotImpacts(byte *renderBitmap, int pitch, int width, int height); // FUN_402DA8 (Handler 8)
 
 	// Draw fallback ship using embedded HUD frame
 	void renderFallbackShip(byte *renderBitmap, int pitch, int width, int height);
@@ -614,6 +615,7 @@ public:
 	Common::Point getHandler7ShipDrawPoint();
 	Common::Point getHandler7ProjectedPoint();
 	Common::Point getHandler7ShotTargetPoint();
+	Common::Point getHandler8ShotTargetPoint();
 
 	// Reset enemy active flags and collision zones at frame end
 	void frameEndCleanup();
@@ -969,6 +971,21 @@ public:
 	};
 	VehicleShot _vehicleShots[2];
 
+	// Handler 8 shot impact animation state (FUN_402DA8 / FUN_402ED0)
+	// DAT_0043f81a[7]: impact duration counter
+	// DAT_0043f828[7]: impact world X
+	// DAT_0043f836[7]: impact world Y
+	// DAT_0043f844[7]: impact sprite index from DAT_0047e030 background mask
+	// DAT_0043f852: ring-buffer index
+	struct VehicleShotImpact {
+		int16 counter;
+		int16 x;
+		int16 y;
+		int16 spriteIndex;
+	};
+	VehicleShotImpact _vehicleShotImpacts[7];
+	int16 _vehicleShotImpactIndex;
+
 	// ---------------------------------------------------------------------------
 	// Handler 7 Third-Person Ship Shot System
 	// ---------------------------------------------------------------------------
@@ -1035,16 +1052,16 @@ public:
 	// Based on FUN_00401234 and FUN_00401ccf disassembly:
 	// - DAT_0047e010: Primary ship sprite (POV001, subcase 1)
 	// - DAT_0047e028: Secondary ship sprite (POV004, subcase 3)
-	// - DAT_0047e020: Additional overlay (POV002, subcase 6)
-	// - DAT_0047e018: Additional overlay (POV003, subcase 7)
+	// - DAT_0047e020: Shot impact overlay (POV002, subcase 6)
+	// - DAT_0047e018: Shot impact overlay (POV003, subcase 7)
 	// - DAT_0043e006: Ship X position (raw, needs conversion for display)
 	// - DAT_0043e008: Ship Y position (raw, needs conversion for display)
-	// - DAT_0043e000: Level mode from opcode 6 par3
+	// - DAT_0043e000: Level mode from opcode 6 par4
 
 	NutRenderer *_shipSprite;        // DAT_0047e010 - Primary ship NUT
 	NutRenderer *_shipSprite2;       // DAT_0047e028 - Secondary ship NUT
-	NutRenderer *_shipOverlay1;      // DAT_0047e020 - Additional overlay
-	NutRenderer *_shipOverlay2;      // DAT_0047e018 - Additional overlay
+	NutRenderer *_shipOverlay1;      // DAT_0047e020 - Shot impact overlay
+	NutRenderer *_shipOverlay2;      // DAT_0047e018 - Shot impact overlay
 
 	// Level 2 background buffer (DAT_0047e030)
 	// Loaded from IACT opcode 8, par4=5 - contains 320x200 background image
diff --git a/engines/scumm/insane/rebel2/render.cpp b/engines/scumm/insane/rebel2/render.cpp
index afabee2e76a..97d32232cc0 100644
--- a/engines/scumm/insane/rebel2/render.cpp
+++ b/engines/scumm/insane/rebel2/render.cpp
@@ -589,6 +589,13 @@ Common::Point InsaneRebel2::getHandler7ShotTargetPoint() {
 	                     projected.y + ABS(_smoothedVelocity) / 4 - _verticalInput / 2 - 0x28);
 }
 
+Common::Point InsaneRebel2::getHandler8ShotTargetPoint() {
+	// Retail handler 8 stores and draws the shot target from the damped ship
+	// position in FUN_00401ccf, not from the current mouse/analog aim point.
+	return Common::Point(((_shipPosX - 0xa0) >> 3) + 0xa0,
+	                     ((_shipPosY - 0x28) >> 2) + 0x69);
+}
+
 // spawnSpaceShot -- Handler 7 space combat shot spawn (FUN_40D836).
 void InsaneRebel2::spawnSpaceShot(int x, int y) {
 	for (int i = 0; i < 2; i++) {
@@ -2484,6 +2491,7 @@ void InsaneRebel2::renderGameplayPostFrame(byte *renderBitmap, int pitch, int wi
 		_flyShipSprite ? _flyShipSprite->getNumChars() : 0);
 
 	renderHandler7Ship(renderBitmap, pitch, width, height);
+	renderVehicleShotImpacts(renderBitmap, pitch, width, height);
 	renderHandler8Ship(renderBitmap, pitch, width, height);
 	renderFallbackShip(renderBitmap, pitch, width, height);
 
@@ -3295,29 +3303,6 @@ void InsaneRebel2::renderHandler8Ship(byte *renderBitmap, int pitch, int width,
 		}
 	}
 
-	// Also render ship overlays (POV002 / POV003) if loaded.
-	// These are weapon/character overlays loaded via IACT opcode 8 par4=6,7.
-	if (_shipOverlay1) {
-		int overlayIdx = _shipFiring ? 1 : 0;
-		if (overlayIdx >= _shipOverlay1->getNumChars())
-			overlayIdx = 0;
-		int16 ovlXOff = _shipOverlay1->getCharXOffset(overlayIdx);
-		int16 ovlYOff = _shipOverlay1->getCharYOffset(overlayIdx);
-		int ovlX = displayOffsetX + ovlXOff + _viewX;
-		int ovlY = displayOffsetY + ovlYOff + _viewY;
-		renderNutSprite(renderBitmap, pitch, width, height, ovlX, ovlY, _shipOverlay1, overlayIdx);
-	}
-	if (_shipOverlay2) {
-		int overlayIdx = _shipFiring ? 1 : 0;
-		if (overlayIdx >= _shipOverlay2->getNumChars())
-			overlayIdx = 0;
-		int16 ovlXOff = _shipOverlay2->getCharXOffset(overlayIdx);
-		int16 ovlYOff = _shipOverlay2->getCharYOffset(overlayIdx);
-		int ovlX = displayOffsetX + ovlXOff + _viewX;
-		int ovlY = displayOffsetY + ovlYOff + _viewY;
-		renderNutSprite(renderBitmap, pitch, width, height, ovlX, ovlY, _shipOverlay2, overlayIdx);
-	}
-
 	int sprW = _shipSprite->getCharWidth(spriteIndex);
 	int sprH = _shipSprite->getCharHeight(spriteIndex);
 	debug("Rebel2 Handler8: Ship at (%d,%d) raw(%d,%d) offset(%d,%d) nutOff(%d,%d) size(%d,%d) bottom=%d view(%d,%d) sprite=%d/%d",
@@ -3326,6 +3311,41 @@ void InsaneRebel2::renderHandler8Ship(byte *renderBitmap, int pitch, int width,
 		_viewX, _viewY, spriteIndex, numSprites);
 }
 
+// renderVehicleShotImpacts -- Handler 8 shot impact sprites (FUN_402DA8).
+void InsaneRebel2::renderVehicleShotImpacts(byte *renderBitmap, int pitch, int width, int height) {
+	if (_rebelHandler != 8)
+		return;
+
+	for (int i = 0; i < 7; i++) {
+		VehicleShotImpact &impact = _vehicleShotImpacts[i];
+		if (impact.counter <= 0)
+			continue;
+
+		int drawX = impact.x - _shipPosX + _viewX;
+		int drawY = impact.y - _shipPosY + _viewY;
+
+		// Original draws DAT_0047e020 repeatedly based on remaining life, then
+		// DAT_0047e018 once, both using the sampled background-mask sprite index.
+		if (_shipOverlay1 && impact.spriteIndex >= 0 && impact.spriteIndex < _shipOverlay1->getNumChars()) {
+			int spriteX = drawX + _shipOverlay1->getCharXOffset(impact.spriteIndex) -
+				_shipOverlay1->getCharWidth(impact.spriteIndex) / 2;
+			int spriteY = drawY + _shipOverlay1->getCharYOffset(impact.spriteIndex) -
+				_shipOverlay1->getCharHeight(impact.spriteIndex) / 2;
+			for (int repeat = 0; repeat <= (impact.counter >> 2); repeat++)
+				renderNutSprite(renderBitmap, pitch, width, height, spriteX, spriteY, _shipOverlay1, impact.spriteIndex);
+		}
+		if (_shipOverlay2 && impact.spriteIndex >= 0 && impact.spriteIndex < _shipOverlay2->getNumChars()) {
+			int spriteX = drawX + _shipOverlay2->getCharXOffset(impact.spriteIndex) -
+				_shipOverlay2->getCharWidth(impact.spriteIndex) / 2;
+			int spriteY = drawY + _shipOverlay2->getCharYOffset(impact.spriteIndex) -
+				_shipOverlay2->getCharHeight(impact.spriteIndex) / 2;
+			renderNutSprite(renderBitmap, pitch, width, height, spriteX, spriteY, _shipOverlay2, impact.spriteIndex);
+		}
+
+		impact.counter--;
+	}
+}
+
 // Handler 25: Draw GRD001 (wall/cockpit overlay) in procPostRendering.
 // renderHandler25ShipPre -- Handler 25 GRD001 pre-rendering (FUN_0041DB5E lines 202-221).
 // GRD sprites drawn AFTER FOBJ enemies, before GRD002. Mode-based clipping:
@@ -4045,6 +4065,32 @@ void InsaneRebel2::renderVehicleLaserShots(byte *renderBitmap, int pitch, int wi
 			progress, maxDuration, 20, 8, 4);
 
 		_vehicleShots[i].counter--;
+
+		// FUN_402ED0 samples DAT_0047e030 after drawing the beam. Non-zero
+		// mask pixels select the POV002/POV003 impact sprite index rendered by
+		// FUN_402DA8 on following frames. Mode 2 suppresses these impacts.
+		if (_shipLevelMode != 2 && _level2BackgroundLoaded && _level2Background) {
+			int impactX = ((_shipPosX - 160) >> 3) + _shipPosX + 160;
+			int impactY = ((_shipPosY - 40) >> 2) + _shipPosY + 105;
+			int maskX = impactX - 160;
+			int maskY = impactY - 30;
+
+			if (impactX > 160 && impactX < 480 &&
+					maskX >= 0 && maskX < 320 && maskY >= 0 && maskY < 200) {
+				byte spriteIndex = _level2Background[maskY * 320 + maskX];
+				if (spriteIndex != 0) {
+					VehicleShotImpact &impact = _vehicleShotImpacts[_vehicleShotImpactIndex];
+					_vehicleShotImpactIndex++;
+					if (_vehicleShotImpactIndex >= 7)
+						_vehicleShotImpactIndex = 0;
+
+					impact.counter = 12;
+					impact.x = impactX;
+					impact.y = impactY;
+					impact.spriteIndex = spriteIndex;
+				}
+			}
+		}
 	}
 }
 
@@ -4130,7 +4176,7 @@ void InsaneRebel2::renderHandler25LaserShots(byte *renderBitmap, int pitch, int
 	}
 }
 
-// renderCrosshair -- Draw crosshair/reticle at mouse position.
+// renderCrosshair -- Draw crosshair/reticle at the current handler's aim point.
 void InsaneRebel2::renderCrosshair(byte *renderBitmap, int pitch, int width, int height) {
 	// From FUN_0040d836 (Handler 7) line 167-168: crosshair only drawn when DAT_004437c0 == 2
 	// Don't draw crosshair when shooting is disabled (flight-only segments)
@@ -4147,7 +4193,14 @@ void InsaneRebel2::renderCrosshair(byte *renderBitmap, int pitch, int width, int
 	// Update target lock state and draw crosshair/reticle
 
 	// Target lock detection (DAT_00443676 equivalent)
-	Common::Point aimPos = (_rebelHandler == 7) ? getHandler7ShotTargetPoint() : getGameplayAimPoint();
+	Common::Point aimPos;
+	if (_rebelHandler == 7) {
+		aimPos = getHandler7ShotTargetPoint();
+	} else if (_rebelHandler == 8) {
+		aimPos = getHandler8ShotTargetPoint();
+	} else {
+		aimPos = getGameplayAimPoint();
+	}
 	Common::Point worldMousePos = aimPos;
 	if (_rebelHandler != 7) {
 		worldMousePos.x += _viewX;
@@ -4199,8 +4252,8 @@ void InsaneRebel2::renderCrosshair(byte *renderBitmap, int pitch, int width, int
 		int ch = _smush_iconsNut->getCharHeight(reticleIndex);
 
 		// Calculate crosshair position
-		int crosshairX = aimPos.x - cw / 2;
-		int crosshairY = aimPos.y - ch / 2;
+		int crosshairX = aimPos.x;
+		int crosshairY = aimPos.y;
 		if (_rebelHandler != 7) {
 			crosshairX += _viewX;
 			crosshairY += _viewY;
@@ -4213,6 +4266,10 @@ void InsaneRebel2::renderCrosshair(byte *renderBitmap, int pitch, int width, int
 			crosshairY += _rebelViewOffsetY;
 		}
 
+		// FUN_004236e0 with flags=2 applies the NUT frame offsets, then centers.
+		crosshairX += _smush_iconsNut->getCharXOffset(reticleIndex) - cw / 2;
+		crosshairY += _smush_iconsNut->getCharYOffset(reticleIndex) - ch / 2;
+
 		renderNutSprite(renderBitmap, pitch, width, height,
 			crosshairX, crosshairY,
 			_smush_iconsNut, reticleIndex);


Commit: 3f6b1ed8e45ffcd86415bf13754c70eb33749f7b
    https://github.com/scummvm/scummvm/commit/3f6b1ed8e45ffcd86415bf13754c70eb33749f7b
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-06-03T19:34:28+02:00

Commit Message:
SCUMM: RA2: render overlay in level 12

Changed paths:
    engines/scumm/insane/rebel2/iact.cpp
    engines/scumm/insane/rebel2/rebel.cpp
    engines/scumm/insane/rebel2/rebel.h
    engines/scumm/insane/rebel2/render.cpp
    engines/scumm/smush/rebel/smush_player_ra2.cpp
    engines/scumm/smush/rebel/smush_player_ra2.h


diff --git a/engines/scumm/insane/rebel2/iact.cpp b/engines/scumm/insane/rebel2/iact.cpp
index 60c194e6a0d..854fcf100df 100644
--- a/engines/scumm/insane/rebel2/iact.cpp
+++ b/engines/scumm/insane/rebel2/iact.cpp
@@ -713,6 +713,15 @@ void InsaneRebel2::handleOpcode6Handler8(Common::SeekableReadStream &b, int16 pa
 			_shipPosY = (newY < _shipTargetY) ? _shipTargetY : newY;
 		}
 
+		// FUN_00401234 calls FUN_00424510(-DAT_0043e006, -DAT_0043e008)
+		// after updating the handler-8 camera. This shifts subsequent FOBJ
+		// decoding into screen coordinates; FUN_00401CCF then draws HUD and
+		// weapon sprites without a separate final-buffer scroll.
+		if (_player) {
+			_player->_fobjOffsetX = -_shipPosX;
+			_player->_fobjOffsetY = -_shipPosY;
+		}
+
 		// Calculate ship direction indices for sprite selection
 		// Map mouse position to 5x7 direction grid (like Handler 7)
 		int16 mouseX = aimPos.x;
diff --git a/engines/scumm/insane/rebel2/rebel.cpp b/engines/scumm/insane/rebel2/rebel.cpp
index a3acf396b74..4d125c67016 100644
--- a/engines/scumm/insane/rebel2/rebel.cpp
+++ b/engines/scumm/insane/rebel2/rebel.cpp
@@ -160,10 +160,11 @@ InsaneRebel2::InsaneRebel2(ScummEngine_v7 *scumm) {
 	//   Font 0 (^f00): TALKFONT.NUT - Default menu font (DAT_00485058)
 	//   Font 1 (^f01): SMALFONT.NUT - Small font for format code switching
 	//   Font 2 (^f02): TITLFONT.NUT - Title font
-	//   Font 3 (^f03): POVFONT.NUT - POV font (not loaded here)
+	//   Font 3 (^f03): POVFONT.NUT - POV font
 	_smush_talkfontNut = makeRebel2Font(_vm, "SYSTM/TALKFONT.NUT");
 	_smush_smalfontNut = makeRebel2Font(_vm, "SYSTM/SMALFONT.NUT");
 	_smush_titlefontNut = makeRebel2Font(_vm, "SYSTM/TITLFONT.NUT");
+	_smush_povfontNut = makeRebel2Font(_vm, "SYSTM/POVFONT.NUT");
 
 	_pauseOverlayActive = false;
 	memset(_savedPausePalette, 0, sizeof(_savedPausePalette));
@@ -543,6 +544,9 @@ InsaneRebel2::InsaneRebel2(ScummEngine_v7 *scumm) {
 	_gameplaySectionActive = false;
 	_lastGameplayMenuCloseTime = 0;
 	_lastMenuGamepadNavigationTime = 0;
+	_handler8HudGlyph = '#';
+	_handler8HudMessageTimer = 0;
+	_handler8HudMessageIndex = 0;
 
 	// Initialize level state tracking for multi-phase levels
 	_currentPhase = 1;
@@ -566,6 +570,7 @@ InsaneRebel2::~InsaneRebel2() {
 	delete _smush_talkfontNut;
 	delete _smush_smalfontNut;
 	delete _smush_titlefontNut;
+	delete _smush_povfontNut;
 
 	// Clean up Handler 8 ship sprites
 	delete _shipSprite;
@@ -1533,7 +1538,10 @@ int32 InsaneRebel2::processMouse() {
 
 		// Calculate world position for hit testing
 		Common::Point worldMousePos = mousePos;
-		if (_rebelHandler != 7) {
+		if (_rebelHandler == 8) {
+			worldMousePos.x += _shipPosX;
+			worldMousePos.y += _shipPosY;
+		} else if (_rebelHandler != 7) {
 			worldMousePos.x += _viewX;
 			worldMousePos.y += _viewY;
 		}
@@ -1634,7 +1642,8 @@ int32 InsaneRebel2::processMouse() {
 				// Play enemy death sound.
 				// Pan based on enemy center X position: (screenX - 160) mapped to [-127,127]
 				{
-					int enemyCenterX = (it->rect.left + it->rect.right) / 2 - _viewX;
+					int cameraX = (_rebelHandler == 8) ? _shipPosX : _viewX;
+					int enemyCenterX = (it->rect.left + it->rect.right) / 2 - cameraX;
 					int sfxPan = CLIP((enemyCenterX - 160) * 127 / 160, -127, 127);
 					if (_rebelHandler == 8 && it->type >= 1 && it->type <= 4) {
 						// Handler 8 soldier types 1-4: play from auxiliary buffer 0
diff --git a/engines/scumm/insane/rebel2/rebel.h b/engines/scumm/insane/rebel2/rebel.h
index 54489204056..645596603c4 100644
--- a/engines/scumm/insane/rebel2/rebel.h
+++ b/engines/scumm/insane/rebel2/rebel.h
@@ -494,6 +494,7 @@ public:
 	NutRenderer *_smush_talkfontNut;   // Font 0 - primary menu font (DAT_00485058)
 	NutRenderer *_smush_smalfontNut;   // Font 1 - small font for ^f01 switching
 	NutRenderer *_smush_titlefontNut;  // Font 2 - title font
+	NutRenderer *_smush_povfontNut;    // Font 3 - POV font for Handler 8 overlay
 
 	// Saved palette for pause overlay restoration (FUN_405A21)
 	byte _savedPausePalette[768];
@@ -612,6 +613,7 @@ public:
 
 	// Update target lock state and draw crosshair/reticle
 	void renderCrosshair(byte *renderBitmap, int pitch, int width, int height);
+	void renderHandler8PovOverlay(byte *renderBitmap, int pitch, int width, int height);
 	Common::Point getHandler7ShipDrawPoint();
 	Common::Point getHandler7ProjectedPoint();
 	Common::Point getHandler7ShotTargetPoint();
@@ -1086,6 +1088,9 @@ public:
 	// Mode 4: "Autopilot" - no shooting, scripted movement
 	// Mode 5: "Cutscene" - ship not rendered
 	int16 _shipLevelMode;            // DAT_0043e000
+	char _handler8HudGlyph;          // DAT_0047e048
+	int16 _handler8HudMessageTimer;  // DAT_0047e040
+	int16 _handler8HudMessageIndex;  // DAT_0047e044
 
 	// Movement range limiter for Handler 8 (Level 2 covered/shooting states)
 	// Controls horizontal movement range: 127 for shooting, 41 for covered
diff --git a/engines/scumm/insane/rebel2/render.cpp b/engines/scumm/insane/rebel2/render.cpp
index 97d32232cc0..9bdb7e7583a 100644
--- a/engines/scumm/insane/rebel2/render.cpp
+++ b/engines/scumm/insane/rebel2/render.cpp
@@ -63,6 +63,66 @@ static int countEmbeddedFramePixels(const InsaneRebel2::EmbeddedSanFrame &frame)
 	return count;
 }
 
+static bool parseRebel2TextOverlayFormat(const char *&str, NutRenderer *&curFont, int &curColor,
+		NutRenderer **fonts, int numFonts, NutRenderer *defaultFont) {
+	int fontId = InsaneRebel2::parseFormatCode(str, curColor);
+	if (fontId >= 0) {
+		curFont = (fontId < numFonts && fonts[fontId]) ? fonts[fontId] : defaultFont;
+		return true;
+	}
+
+	return fontId == -2;
+}
+
+static const char *getHandler8PovOverlayString(int id) {
+	switch (id) {
+	case 200:
+		return "^f03^c248 MODE  ^c005 INFARED";
+	case 201:
+		return "^f03^c248 SCANNING";
+	case 202:
+		return "^f03^c005 VHL EMM %lx";
+	case 203:
+		return "^f03^c005 SCOOBYNESS FACTOR %lx";
+	case 204:
+		return "^f03^c005 USELESS COORDINATES %lx %lx";
+	case 205:
+		return "^f03^c248 Buy Afterlife";
+	case 206:
+		return "^f03^c248 No Homework.. You must fight the bear";
+	case 207:
+		return "^f03^c248 What is that watermelon doing there?";
+	case 208:
+		return "^f03^c248 I just like to say taboo";
+	case 209:
+		return "^f03^c248 How about them Bears";
+	case 210:
+		return "^f03^c248 There is cause to be optimistic -HAL";
+	case 211:
+		return "^f03^c248 Little Darth is between his legs -LISA";
+	default:
+		return nullptr;
+	}
+}
+
+static int getHandler8PovOverlayRandom(ScummEngine_v7 *vm, int max) {
+	if (max == 0)
+		return 0;
+	if (max < 0)
+		return -vm->_rnd.getRandomNumber(-max - 1);
+	return vm->_rnd.getRandomNumber(max - 1);
+}
+
+static void drawHandler8PovOverlayText(const Rebel2FontSet &fontSet, byte *renderBitmap,
+		int pitch, int width, int height, const char *str, int x, int y, int16 color,
+		TextStyleFlags flags) {
+	if (!str)
+		return;
+
+	Common::Rect clipRect(0, 0, width, height);
+	drawRebel2String(fontSet, str, strlen(str), renderBitmap, clipRect, x, y, pitch, color, flags);
+}
+
 static void blitEmbeddedFrameRegion(byte *renderBitmap, int pitch, int clipWidth, int clipHeight,
 		const InsaneRebel2::EmbeddedSanFrame &frame, int destX, int destY,
 		int srcX, int srcY, int drawWidth, int drawHeight) {
@@ -462,8 +522,8 @@ void InsaneRebel2::spawnVehicleShot(int x, int y) {
 			// FUN_0041189e(6, local_c + 1, 0x7f, 0, 0) — HBLAST.SAD
 			playSfx(6, 127, 0);
 			_vehicleShots[i].counter = getShotMaxDuration();
-			_vehicleShots[i].targetX = x + _viewX;
-			_vehicleShots[i].targetY = y + _viewY;
+			_vehicleShots[i].targetX = x;
+			_vehicleShots[i].targetY = y;
 			break;
 		}
 	}
@@ -2187,6 +2247,16 @@ void InsaneRebel2::renderNutSpriteMirrored(byte *dst, int pitch, int width, int
 
 // updatePostRenderScroll -- Set SmushPlayer scroll offsets for the current frame.
 void InsaneRebel2::updatePostRenderScroll(int width, int height) {
+	if (_rebelHandler == 8) {
+		// Handler 8 follows FUN_00401234/FUN_00401CCF: the camera is applied
+		// through FUN_00424510 before FOBJ decoding, not by scrolling the final
+		// buffer copied by FUN_00424540.
+		_viewX = 0;
+		_viewY = 0;
+		_player->setScrollOffset(0, 0);
+		return;
+	}
+
 	// Rebel Assault 2 uses a buffer larger (424x260) than screen (320x200).
 	// Map mouse X (0-320) to Scroll X (0-104), and Y (0-200) to Scroll Y (0-60).
 	int maxScrollX = width - _vm->_screenWidth;
@@ -2529,6 +2599,9 @@ void InsaneRebel2::renderGameplayPostFrame(byte *renderBitmap, int pitch, int wi
 	// Crosshair/reticle (FUN_004089ab, FUN_0040d836).
 	renderCrosshair(renderBitmap, pitch, width, height);
 
+	// Handler 8 POV text overlay (FUN_00401CCF).
+	renderHandler8PovOverlay(renderBitmap, pitch, width, height);
+
 	// HUD score/lives rendering (FUN_0041c012).
 	renderScoreHUD(renderBitmap, pitch, width, height, statusBarY);
 
@@ -2740,13 +2813,6 @@ void InsaneRebel2::renderTextOverlay(byte *renderBitmap, int pitch, int width, i
 
 	Common::Rect clipRect(0, 0, width, height);
 
-	// Wrapper around shared parseFormatCode that also updates curFont
-	auto parseFormat = [&](const char *&s, NutRenderer *&curFont, int &curColor) {
-		int fc = parseFormatCode(s, curColor);
-		if (fc >= 0) { curFont = (fonts[fc] ? fonts[fc] : defaultFont); return true; }
-		return fc == -2;
-	};
-
 	// Split into lines, then render each centered at textX (FUN_004341a0).
 	// Older RA2 text loading joined multi-line strings with spaces, leaving
 	// " ^f" as the separator; current TRES loading preserves real newlines.
@@ -2793,7 +2859,7 @@ void InsaneRebel2::renderTextOverlay(byte *renderBitmap, int pitch, int width, i
 			NutRenderer *mFont = defaultFont;
 			int mColor = 1;
 			while (s < lineEnd && (visCount + lineVisCount) < displayLen) {
-				if (parseFormat(s, mFont, mColor))
+				if (parseRebel2TextOverlayFormat(s, mFont, mColor, fonts, ARRAYSIZE(fonts), defaultFont))
 					continue;
 				lineFont = mFont;
 				byte c = (byte)*s++;
@@ -2813,7 +2879,7 @@ void InsaneRebel2::renderTextOverlay(byte *renderBitmap, int pitch, int width, i
 			NutRenderer *curFont = defaultFont;
 			int curColor = 1;
 			while (s < lineEnd && (visCount + lineCharsDrawn) < displayLen) {
-				if (parseFormat(s, curFont, curColor))
+				if (parseRebel2TextOverlayFormat(s, curFont, curColor, fonts, ARRAYSIZE(fonts), defaultFont))
 					continue;
 				byte c = (byte)*s++;
 				if (c >= 'a' && c <= 'z')
@@ -3285,8 +3351,8 @@ void InsaneRebel2::renderHandler8Ship(byte *renderBitmap, int pitch, int width,
 	// (centered for Level 2/11 third-person, bottom for Level 12 FPS).
 	int16 spriteXOffset = _shipSprite->getCharXOffset(spriteIndex);
 	int16 spriteYOffset = _shipSprite->getCharYOffset(spriteIndex);
-	int drawX = displayOffsetX + spriteXOffset + _viewX;
-	int drawY = displayOffsetY + spriteYOffset + _viewY;
+	int drawX = displayOffsetX + spriteXOffset;
+	int drawY = displayOffsetY + spriteYOffset;
 
 	renderNutSprite(renderBitmap, pitch, width, height, drawX, drawY, _shipSprite, spriteIndex);
 
@@ -3297,8 +3363,8 @@ void InsaneRebel2::renderHandler8Ship(byte *renderBitmap, int pitch, int width,
 		if (shadowIndex < _shipSprite2->getNumChars()) {
 			int16 shadowXOff = _shipSprite2->getCharXOffset(shadowIndex);
 			int16 shadowYOff = _shipSprite2->getCharYOffset(shadowIndex);
-			int shadowX = displayOffsetX + shadowXOff + _viewX;
-			int shadowY = displayOffsetY + shadowYOff + _viewY;
+			int shadowX = displayOffsetX + shadowXOff;
+			int shadowY = displayOffsetY + shadowYOff;
 			renderNutSprite(renderBitmap, pitch, width, height, shadowX, shadowY, _shipSprite2, shadowIndex);
 		}
 	}
@@ -3307,7 +3373,7 @@ void InsaneRebel2::renderHandler8Ship(byte *renderBitmap, int pitch, int width,
 	int sprH = _shipSprite->getCharHeight(spriteIndex);
 	debug("Rebel2 Handler8: Ship at (%d,%d) raw(%d,%d) offset(%d,%d) nutOff(%d,%d) size(%d,%d) bottom=%d view(%d,%d) sprite=%d/%d",
 		drawX, drawY, _shipPosX, _shipPosY, displayOffsetX, displayOffsetY,
-		spriteXOffset, spriteYOffset, sprW, sprH, drawY + sprH - _viewY,
+		spriteXOffset, spriteYOffset, sprW, sprH, drawY + sprH,
 		_viewX, _viewY, spriteIndex, numSprites);
 }
 
@@ -3321,8 +3387,8 @@ void InsaneRebel2::renderVehicleShotImpacts(byte *renderBitmap, int pitch, int w
 		if (impact.counter <= 0)
 			continue;
 
-		int drawX = impact.x - _shipPosX + _viewX;
-		int drawY = impact.y - _shipPosY + _viewY;
+		int drawX = impact.x - _shipPosX;
+		int drawY = impact.y - _shipPosY;
 
 		// Original draws DAT_0047e020 repeatedly based on remaining life, then
 		// DAT_0047e018 once, both using the sampled background-mask sprite index.
@@ -3791,7 +3857,7 @@ void InsaneRebel2::renderTurretExplosions(byte *renderBitmap, int pitch, int wid
 }
 
 // renderVehicleExplosions -- Handler 8 on-foot explosion rendering (FUN_402696).
-// Position: world coords minus camera offset (DAT_0043e006/08 = _viewX/_viewY).
+// Position: world coords minus camera offset (DAT_0043e006/08 = _shipPosX/_shipPosY).
 // Scale thresholds: <11, <21. No secondary NUT.
 void InsaneRebel2::renderVehicleExplosions(byte *renderBitmap, int pitch, int width, int height) {
 	if (!_smush_iconsNut)
@@ -3802,8 +3868,8 @@ void InsaneRebel2::renderVehicleExplosions(byte *renderBitmap, int pitch, int wi
 			continue;
 
 		// FUN_402696 line 22-23: screenX = worldX - DAT_0043e006, screenY = worldY - DAT_0043e008
-		int screenX = _explosions[i].x - _viewX;
-		int screenY = _explosions[i].y - _viewY;
+		int screenX = _explosions[i].x - _shipPosX;
+		int screenY = _explosions[i].y - _shipPosY;
 		renderExplosionFrame(renderBitmap, pitch, width, height, _explosions[i],
 			screenX, screenY, kExplosionAdvanceAfterDraw);
 	}
@@ -4060,8 +4126,8 @@ void InsaneRebel2::renderVehicleLaserShots(byte *renderBitmap, int pitch, int wi
 		// From FUN_402ED0: widthScale=0x14(20), heightScale=8, thickness=4
 		// Parameters: gunX, gunY -> shipScreenX, shipScreenY (NOT the stored target!)
 		drawLaserBeam(renderBitmap, pitch, width, height,
-			gunX + _viewX, gunY + _viewY,
-			shipScreenX + _viewX, shipScreenY + _viewY,
+			gunX, gunY,
+			shipScreenX, shipScreenY,
 			progress, maxDuration, 20, 8, 4);
 
 		_vehicleShots[i].counter--;
@@ -4176,6 +4242,69 @@ void InsaneRebel2::renderHandler25LaserShots(byte *renderBitmap, int pitch, int
 	}
 }
 
+// renderHandler8PovOverlay -- Draw Level 12 POV text overlay (FUN_00401CCF).
+void InsaneRebel2::renderHandler8PovOverlay(byte *renderBitmap, int pitch, int width, int height) {
+	if (_rebelHandler != 8 || !renderBitmap || !_smush_talkfontNut || !_smush_povfontNut)
+		return;
+
+	Rebel2FontSet fontSet;
+	fontSet.numFonts = 4;
+	fontSet.defaultFont = 0;
+	fontSet.fonts[0] = _smush_talkfontNut;
+	fontSet.fonts[1] = _smush_smalfontNut ? _smush_smalfontNut : _smush_talkfontNut;
+	fontSet.fonts[2] = _smush_titlefontNut ? _smush_titlefontNut : _smush_talkfontNut;
+	fontSet.fonts[3] = _smush_povfontNut;
+
+	ScummEngine_v7 *vm = (ScummEngine_v7 *)_vm;
+
+	// Original updates DAT_0047e048 with random(5)+0x23 when random(20)==0.
+	if (getHandler8PovOverlayRandom(vm, 20) == 0)
+		_handler8HudGlyph = (char)(getHandler8PovOverlayRandom(vm, 5) + 0x23);
+
+	drawHandler8PovOverlayText(fontSet, renderBitmap, pitch, width, height,
+		getHandler8PovOverlayString(200), 10, 5, 1, kStyleAlignLeft);
+
+	char buffer[128];
+	Common::sprintf_s(buffer, "^f03&#$%c", _handler8HudGlyph);
+	drawHandler8PovOverlayText(fontSet, renderBitmap, pitch, width, height,
+		buffer, 10, 150, 248, kStyleAlignLeft);
+
+	const char *text = getHandler8PovOverlayString(203);
+	if (text) {
+		Common::sprintf_s(buffer, text,
+			(unsigned long)(uint32)(int32)getHandler8PovOverlayRandom(vm, 20000));
+		drawHandler8PovOverlayText(fontSet, renderBitmap, pitch, width, height,
+			buffer, 10, 170, 1, kStyleAlignLeft);
+	}
+
+	text = getHandler8PovOverlayString(202);
+	if (text) {
+		Common::sprintf_s(buffer, text,
+			(unsigned long)(uint32)(int32)getHandler8PovOverlayRandom(vm, -0xd50));
+		drawHandler8PovOverlayText(fontSet, renderBitmap, pitch, width, height,
+			buffer, 220, 160, 1, kStyleAlignLeft);
+	}
+
+	text = getHandler8PovOverlayString(204);
+	if (text) {
+		Common::sprintf_s(buffer, text, (unsigned long)(uint32)(int32)(int16)_shipPosX,
+			(unsigned long)(uint32)(int32)(int16)_shipPosY);
+		drawHandler8PovOverlayText(fontSet, renderBitmap, pitch, width, height,
+			buffer, 220, 170, 1, kStyleAlignLeft);
+	}
+
+	if (_handler8HudMessageTimer == 0) {
+		_handler8HudMessageIndex = getHandler8PovOverlayRandom(vm, 100);
+		if (_handler8HudMessageIndex < 7)
+			_handler8HudMessageTimer = 0x32;
+	} else {
+		_handler8HudMessageTimer--;
+		drawHandler8PovOverlayText(fontSet, renderBitmap, pitch, width, height,
+			getHandler8PovOverlayString(_handler8HudMessageIndex + 0xcd),
+			200, 5, 1, kStyleAlignCenter);
+	}
+}
+
 // renderCrosshair -- Draw crosshair/reticle at the current handler's aim point.
 void InsaneRebel2::renderCrosshair(byte *renderBitmap, int pitch, int width, int height) {
 	// From FUN_0040d836 (Handler 7) line 167-168: crosshair only drawn when DAT_004437c0 == 2
@@ -4202,7 +4331,10 @@ void InsaneRebel2::renderCrosshair(byte *renderBitmap, int pitch, int width, int
 		aimPos = getGameplayAimPoint();
 	}
 	Common::Point worldMousePos = aimPos;
-	if (_rebelHandler != 7) {
+	if (_rebelHandler == 8) {
+		worldMousePos.x += _shipPosX;
+		worldMousePos.y += _shipPosY;
+	} else if (_rebelHandler != 7) {
 		worldMousePos.x += _viewX;
 		worldMousePos.y += _viewY;
 	}
diff --git a/engines/scumm/smush/rebel/smush_player_ra2.cpp b/engines/scumm/smush/rebel/smush_player_ra2.cpp
index 3b0d660c7fb..2c43508df0d 100644
--- a/engines/scumm/smush/rebel/smush_player_ra2.cpp
+++ b/engines/scumm/smush/rebel/smush_player_ra2.cpp
@@ -43,11 +43,52 @@ namespace Scumm {
 
 // FUN_00423880 is initialized at startup with a 400000-byte preload buffer.
 constexpr int kRebel2LoadBufferSize = 400000;
+constexpr int kRebel2GameplaySurfaceWidth = 0x1a8;
+constexpr int kRebel2GameplaySurfaceHeight = 0x104;
 
 bool isRebel2FullFrameDeltaCodec(int codec) {
 	return codec == SMUSH_CODEC_DELTA_BLOCKS || codec == SMUSH_CODEC_DELTA_GLYPHS;
 }
 
+bool isRebel2GameplayActive(Insane *insane) {
+	if (insane == nullptr)
+		return false;
+
+	return static_cast<InsaneRebel2 *>(insane)->getHandler() != 0;
+}
+
+void smushDecodeRA2Uncompressed(byte *dst, const byte *src, int left, int top,
+		int width, int height, int dstPitch, int srcPitch, int dataSize,
+		int srcSkipX, int srcSkipY) {
+	if (dst == nullptr || src == nullptr || width <= 0 || height <= 0 ||
+			dstPitch <= 0 || srcPitch <= 0) {
+		return;
+	}
+
+	const int64 srcOffset = (int64)srcSkipY * srcPitch + srcSkipX;
+	if (srcOffset < 0 || (dataSize > 0 && srcOffset >= dataSize))
+		return;
+
+	if (dataSize > 0) {
+		const int64 bytesAfterOffset = (int64)dataSize - srcOffset;
+		if (bytesAfterOffset < width)
+			return;
+
+		const int maxRows = (int)((bytesAfterOffset - width) / srcPitch) + 1;
+		height = MIN(height, maxRows);
+		if (height <= 0)
+			return;
+	}
+
+	src += srcOffset;
+	dst += top * dstPitch + left;
+	while (height-- > 0) {
+		memcpy(dst, src, width);
+		src += srcPitch;
+		dst += dstPitch;
+	}
+}
+
 // ---------------------------------------------------------------------------
 // SmushPlayerRebel2 — construction / destruction
 // ---------------------------------------------------------------------------
@@ -93,7 +134,9 @@ void SmushPlayerRebel2::initGamePlayerFields() {
 	_loadReadOffset = 8;  // Original starts reading at offset 8 (skips header)
 	_lastLoadChunkIdx = -1;
 	_loadStreamId = 0;
+	_ra2FrameSourceSkipX = 0;
 	_ra2FrameSourceSkipY = 0;
+	_ra2FrameObjectOriginalWidth = 0;
 	_ra2FrameObjectSurfaceWidth = 0;
 	_ra2FrameObjectSurfaceHeight = 0;
 	_ra2PendingAnimHeaderPalette = false;
@@ -670,6 +713,7 @@ void SmushPlayerRebel2::ra2HandleTextResource(const char *str, int fontId, int c
  * Returns true when the dimensions are valid and updates _dst, _width, _height as needed.
  */
 void SmushPlayerRebel2::ra2PrepareFrameObjectSurface(int left, int top, int width, int height) {
+	_ra2FrameObjectOriginalWidth = width;
 	_ra2FrameObjectSurfaceWidth = width;
 	_ra2FrameObjectSurfaceHeight = height;
 
@@ -682,15 +726,23 @@ void SmushPlayerRebel2::ra2PrepareFrameObjectSurface(int left, int top, int widt
 }
 
 bool SmushPlayerRebel2::ra2SelectFrameBuffer(int codec, int width, int height) {
-	// Rebel2 uses a special buffer for all non-matching frames.
-	// Oversized full-frame delta codecs decode into a tightly packed surface
-	// with their own width as pitch. Other codecs honor left/top and pitch.
+	// Rebel2 allocates the low-res gameplay target as 424x260 (FUN_00424730).
+	// Use that target once an oversized gameplay FOBJ appears, then keep small
+	// overlay FOBJ chunks compositing into it. Pure 320x200 gameplay videos keep
+	// drawing their small FOBJ chunks directly onto the main screen.
 	const int screenSize = _vm->_screenWidth * _vm->_screenHeight;
 	const int64 fobjSize64 = (int64)width * height;
 	int surfaceWidth = width;
 	int surfaceHeight = height;
 
-	if (fobjSize64 > screenSize && !isRebel2FullFrameDeltaCodec(codec)) {
+	const bool useGameplaySurface = isRebel2GameplayActive(_insane) &&
+		!isRebel2FullFrameDeltaCodec(codec) &&
+		(fobjSize64 > screenSize || _specialBuffer != nullptr);
+
+	if (useGameplaySurface) {
+		surfaceWidth = kRebel2GameplaySurfaceWidth;
+		surfaceHeight = kRebel2GameplaySurfaceHeight;
+	} else if (fobjSize64 > screenSize && !isRebel2FullFrameDeltaCodec(codec)) {
 		surfaceWidth = MAX(surfaceWidth, _ra2FrameObjectSurfaceWidth);
 		surfaceHeight = MAX(surfaceHeight, _ra2FrameObjectSurfaceHeight);
 	}
@@ -752,6 +804,12 @@ bool SmushPlayerRebel2::ra2SelectFrameBuffer(int codec, int width, int height) {
 bool SmushPlayerRebel2::ra2DecodeCodec(int codec, const uint8 *src, int left, int top,
 								 int width, int height, int pitch, int dataSize) {
 	switch (codec) {
+	case SMUSH_CODEC_UNCOMPRESSED: {
+		const int sourcePitch = (_ra2FrameObjectOriginalWidth > 0) ? _ra2FrameObjectOriginalWidth : width;
+		smushDecodeRA2Uncompressed(_dst, src, left, top, width, height, pitch, sourcePitch,
+			dataSize, _ra2FrameSourceSkipX, _ra2FrameSourceSkipY);
+		return true;
+	}
 	case SMUSH_CODEC_LINE_UPDATE:
 	case SMUSH_CODEC_LINE_UPDATE2: {
 		const uint8 *adjustedSrc = smushSkipRLELines(src, dataSize, _ra2FrameSourceSkipY);
@@ -882,13 +940,23 @@ bool SmushPlayerRebel2::handleGameDimensionOverride(int codec, int width, int he
 
 bool SmushPlayerRebel2::handleGameAdjustCoords(int codec, int &left, int &top, int &width, int &height, int pitch, int *srcSkipY) {
 	int sourceSkipY = 0;
+	const int adjustedLeft = left + _fobjOffsetX;
+	_ra2FrameSourceSkipX = (adjustedLeft < 0) ? -adjustedLeft : 0;
 	_ra2FrameSourceSkipY = 0;
 	adjustFrameCoords(left, top, width, height, pitch, &sourceSkipY);
 	if (codec == SMUSH_CODEC_LINE_UPDATE || codec == SMUSH_CODEC_LINE_UPDATE2 ||
-			codec == SMUSH_CODEC_SKIP_RLE || codec == SMUSH_CODEC_RA2_BOMP) {
+			codec == SMUSH_CODEC_SKIP_RLE || codec == SMUSH_CODEC_RA2_BOMP ||
+			codec == SMUSH_CODEC_UNCOMPRESSED) {
 		_ra2FrameSourceSkipY = sourceSkipY;
 		if (srcSkipY)
 			*srcSkipY = 0;
+	} else if (isRebel2FullFrameDeltaCodec(codec)) {
+		// Codec 37/47 streams are full-frame delta streams, not row-prefixed
+		// RLE data. The original FOBJ dispatcher applies FUN_00424510 offsets
+		// to the destination coordinates passed to FUN_0042cba0; it does not
+		// advance the compressed source by clipped scanlines.
+		if (srcSkipY)
+			*srcSkipY = 0;
 	} else if (srcSkipY) {
 		*srcSkipY = sourceSkipY;
 	}
diff --git a/engines/scumm/smush/rebel/smush_player_ra2.h b/engines/scumm/smush/rebel/smush_player_ra2.h
index 35af253e0f6..87c64a12431 100644
--- a/engines/scumm/smush/rebel/smush_player_ra2.h
+++ b/engines/scumm/smush/rebel/smush_player_ra2.h
@@ -83,7 +83,9 @@ private:
 	int32 _loadReadOffset;
 	int16 _lastLoadChunkIdx;
 	int16 _loadStreamId;
+	int _ra2FrameSourceSkipX;
 	int _ra2FrameSourceSkipY;
+	int _ra2FrameObjectOriginalWidth;
 	int _ra2FrameObjectSurfaceWidth;
 	int _ra2FrameObjectSurfaceHeight;
 	bool _ra2PendingAnimHeaderPalette;


Commit: e0985e843eeb5e4e9cb075300c5f71036fc754f8
    https://github.com/scummvm/scummvm/commit/e0985e843eeb5e4e9cb075300c5f71036fc754f8
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-06-03T19:37:44+02:00

Commit Message:
SCUMM: RA2: removed some useless statics

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


diff --git a/engines/scumm/insane/rebel2/render.cpp b/engines/scumm/insane/rebel2/render.cpp
index 9bdb7e7583a..5eae883192b 100644
--- a/engines/scumm/insane/rebel2/render.cpp
+++ b/engines/scumm/insane/rebel2/render.cpp
@@ -63,7 +63,7 @@ static int countEmbeddedFramePixels(const InsaneRebel2::EmbeddedSanFrame &frame)
 	return count;
 }
 
-static bool parseRebel2TextOverlayFormat(const char *&str, NutRenderer *&curFont, int &curColor,
+bool parseRebel2TextOverlayFormat(const char *&str, NutRenderer *&curFont, int &curColor,
 		NutRenderer **fonts, int numFonts, NutRenderer *defaultFont) {
 	int fontId = InsaneRebel2::parseFormatCode(str, curColor);
 	if (fontId >= 0) {
@@ -74,7 +74,7 @@ static bool parseRebel2TextOverlayFormat(const char *&str, NutRenderer *&curFont
 	return fontId == -2;
 }
 
-static const char *getHandler8PovOverlayString(int id) {
+const char *getHandler8PovOverlayString(int id) {
 	switch (id) {
 	case 200:
 		return "^f03^c248 MODE  ^c005 INFARED";
@@ -105,7 +105,7 @@ static const char *getHandler8PovOverlayString(int id) {
 	}
 }
 
-static int getHandler8PovOverlayRandom(ScummEngine_v7 *vm, int max) {
+int getHandler8PovOverlayRandom(ScummEngine_v7 *vm, int max) {
 	if (max == 0)
 		return 0;
 	if (max < 0)
@@ -113,7 +113,7 @@ static int getHandler8PovOverlayRandom(ScummEngine_v7 *vm, int max) {
 	return vm->_rnd.getRandomNumber(max - 1);
 }
 
-static void drawHandler8PovOverlayText(const Rebel2FontSet &fontSet, byte *renderBitmap,
+void drawHandler8PovOverlayText(const Rebel2FontSet &fontSet, byte *renderBitmap,
 		int pitch, int width, int height, const char *str, int x, int y, int16 color,
 		TextStyleFlags flags) {
 	if (!str)




More information about the Scummvm-git-logs mailing list