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

sev- noreply at scummvm.org
Fri Jul 10 23:17:14 UTC 2026


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

Summary:
e62eaad2a8 DIRECTOR: DT: Fix crashes and stale pointer in Cast Details window
eb37fa35bc DIRECTOR: DT: Do not use game data as printf format strings
51f73a6eee DIRECTOR: Return frame labels by value to fix leak in DT score window
ac555dc66e DIRECTOR: DT: Guard Score and Channels windows against missing data
38867cf706 DIRECTOR: DT: Fix breakpoint toggles sharing IDs and use-after-erase
184abcce34 DIRECTOR: DT: Scope Cast window IDs per cast lib and show all casts
7900b36e7a DIRECTOR: DT: Harden script and resource lookups against bad input
7a9c0b1d7c DIRECTOR: DT: Drop CastMember-keyed caches when a movie changes
695080e8c1 DIRECTOR: DT: Fix window selector wiring and small logic slips
20f41e82b7 DIRECTOR: DT: Fix script navigation, highlighting and view controls
28609589d5 DIRECTOR: DT: Show the sprite script in the sprite inspector
10405a1f52 DIRECTOR: DT: Fix movie listing and goto behavior in Windows viewer
fb257ea0e4 DIRECTOR: DT: Resolve sprite members through the movie in the Score


Commit: e62eaad2a8efe195f6d77086c0b7c2240f7726a5
    https://github.com/scummvm/scummvm/commit/e62eaad2a8efe195f6d77086c0b7c2240f7726a5
Author: ramyak-sharma (ramyaksharma1 at gmail.com)
Date: 2026-07-11T01:17:05+02:00

Commit Message:
DIRECTOR: DT: Fix crashes and stale pointer in Cast Details window

The Cast Details window stored a raw CastMember pointer that outlived
the cast member itself: switching movies while the window was open
dereferenced freed memory every frame. Store a CastMemberID instead
and resolve it on each frame, showing a placeholder when the member
no longer exists. The film loop frame map is keyed by ID now for the
same reason.

Also fix ImGui::End() being skipped when the window is collapsed
(it was inside the Begin() conditional), and columnSizeForThumbnail()
converting a division by zero to int (undefined behavior) for cast
members without a thumbnail.

Changed paths:
    engines/director/debugger/dt-cast.cpp
    engines/director/debugger/dt-castdetails.cpp
    engines/director/debugger/dt-internal.h
    engines/director/debugger/dt-score.cpp


diff --git a/engines/director/debugger/dt-cast.cpp b/engines/director/debugger/dt-cast.cpp
index 4176d137348..7ba4d2e04b4 100644
--- a/engines/director/debugger/dt-cast.cpp
+++ b/engines/director/debugger/dt-cast.cpp
@@ -134,7 +134,7 @@ void drawCastRow(Cast* cast) {
 			ImVec2(0, 32.f) // match row height
 		)) {
 			castMember._value->load();
-			_state->_castDetails._castMember = castMember._value;
+			_state->_castDetails._castMemberID = CastMemberID(castMember._key, cast->_castLibID);
 			_state->_w.castDetails = true;
 		}
 		ImGui::SameLine();
@@ -346,7 +346,7 @@ void showCast() {
 						if (ImGui::IsItemHovered() && ImGui::IsMouseClicked(0)) {
 							// Cast Member Clicked
 							castMember._value->load();
-							_state->_castDetails._castMember = castMember._value; // Must set _castMember before making the caseDetails window visible to prevent null castMember
+							_state->_castDetails._castMemberID = CastMemberID(castMember._key, cast->_castLibID);
 							_state->_w.castDetails = true;
 						}
 					}
diff --git a/engines/director/debugger/dt-castdetails.cpp b/engines/director/debugger/dt-castdetails.cpp
index 9033b50e67a..2325c6a54e6 100644
--- a/engines/director/debugger/dt-castdetails.cpp
+++ b/engines/director/debugger/dt-castdetails.cpp
@@ -763,10 +763,14 @@ void drawFilmLoopCMprops(FilmLoopCastMember *member) {
 		}
 
 		// Initialize current frame for this member if needed
+		CastMemberID memberID(member->getID(), member->getCast()->_castLibID);
 		auto &filmLoopFrames = _state->_castDetails._filmLoopCurrentFrame;
-		if (!filmLoopFrames.contains(member))
-			filmLoopFrames[member] = 0;
-		int &currentFrame = filmLoopFrames[member];
+		if (!filmLoopFrames.contains(memberID))
+			filmLoopFrames[memberID] = 0;
+		int &currentFrame = filmLoopFrames[memberID];
+		// The stored frame can be stale if the score cache changed size
+		if (currentFrame >= numFrames)
+			currentFrame = 0;
 
 		const float cellW = 30.0f;
 		const float cellH = 18.0f;
@@ -1074,6 +1078,8 @@ void drawCMTypeProps(CastMember *member) {
 }
 
 int columnSizeForThumbnail(const ImGuiImage& imgID, float imageDrawSize, float padding) {
+	if (imgID.width <= 0 || imgID.height <= 0)
+		return (int)(imageDrawSize + padding);
 	if (imgID.width > imgID.height) {
 		return imageDrawSize + padding;
 	} else {
@@ -1089,8 +1095,13 @@ void showCastDetails() {
 	ImGui::SetNextWindowSize(ImVec2(240, 480), ImGuiCond_FirstUseEver);
 
 	if (ImGui::Begin("Cast Details", &_state->_w.castDetails)) {
-		CastMember *member = _state->_castDetails._castMember;
-		assert(member != nullptr);
+		Movie *movie = g_director->getCurrentMovie();
+		CastMember *member = movie ? movie->getCastMember(_state->_castDetails._castMemberID) : nullptr;
+		if (!member) {
+			ImGui::TextDisabled("No cast member selected");
+			ImGui::End();
+			return;
+		}
 
 		CastType memberType = member->_type;
 
@@ -1131,9 +1142,9 @@ void showCastDetails() {
 
 			ImGui::EndTabBar();
 		}
-
-		ImGui::End();
 	}
+	// End() must be called regardless of what Begin() returned
+	ImGui::End();
 }
 
 } // namespace DT
diff --git a/engines/director/debugger/dt-internal.h b/engines/director/debugger/dt-internal.h
index 6f93bea0a8b..efeb5053a2f 100644
--- a/engines/director/debugger/dt-internal.h
+++ b/engines/director/debugger/dt-internal.h
@@ -229,8 +229,9 @@ typedef struct ImGuiState {
 		Common::HashMap<Window *, ScriptData> _windowScriptData;
 	} _functions;
 	struct {
-		CastMember *_castMember = nullptr;
-		Common::HashMap<CastMember *, int> _filmLoopCurrentFrame;
+		// stored as an ID: raw CastMember pointers dangle on movie switch
+		CastMemberID _castMemberID;
+		Common::HashMap<CastMemberID, int> _filmLoopCurrentFrame;
 	} _castDetails;
 
 	struct {
diff --git a/engines/director/debugger/dt-score.cpp b/engines/director/debugger/dt-score.cpp
index c4422637a0f..7037bcafb20 100644
--- a/engines/director/debugger/dt-score.cpp
+++ b/engines/director/debugger/dt-score.cpp
@@ -746,7 +746,7 @@ static void drawSpriteGrid(ImDrawList *dl, ImVec2 startPos, Score *score, Cast *
 					if (sprite._castId.member) {
 						CastMember *clickedCM = cast->getCastMember(sprite._castId.member, true);
 						if (clickedCM) {
-							_state->_castDetails._castMember = clickedCM;
+							_state->_castDetails._castMemberID = CastMemberID(clickedCM->getID(), clickedCM->getCast()->_castLibID);
 							_state->_w.castDetails = true;
 						}
 					}


Commit: eb37fa35bc3a8a5ef21e6ca51968cb85355b943f
    https://github.com/scummvm/scummvm/commit/eb37fa35bc3a8a5ef21e6ca51968cb85355b943f
Author: ramyak-sharma (ramyaksharma1 at gmail.com)
Date: 2026-07-11T01:17:05+02:00

Commit Message:
DIRECTOR: DT: Do not use game data as printf format strings

Cast member names, palette names, movie paths and restored log lines
were passed directly as the format argument of ImGui::Text and
friends. Any '%' in that data (which games do contain) reads garbage
varargs. Route everything through "%s" or TextUnformatted.

Changed paths:
    engines/director/debugger/dt-castdetails.cpp
    engines/director/debugger/dt-controlpanel.cpp
    engines/director/debugger/dt-save-state.cpp


diff --git a/engines/director/debugger/dt-castdetails.cpp b/engines/director/debugger/dt-castdetails.cpp
index 2325c6a54e6..77f306b762a 100644
--- a/engines/director/debugger/dt-castdetails.cpp
+++ b/engines/director/debugger/dt-castdetails.cpp
@@ -91,7 +91,7 @@ void drawBitmapCMprops(BitmapCastMember *member) {
 		if (ImGui::CollapsingHeader("Media Properties")) {
 			if (ImGui::BeginTable("##BitmapMediaProperties", 2, ImGuiTableFlags_RowBg | ImGuiTableFlags_Borders)) {
 
-				showProperty("paletteRef", (member->_clut.castLib > 0 ? "Custom Palette" : paletteType2str((PaletteType)member->_clut.member).c_str()));
+				showProperty("paletteRef", "%s", (member->_clut.castLib > 0 ? "Custom Palette" : paletteType2str((PaletteType)member->_clut.member).c_str()));
 
 				showPropertyBool("centerRegPoint", (member->_flags1 & BitmapCastMember::kFlagCenterRegPoint) || (member->_flags1 & BitmapCastMember::kFlagCenterRegPointD4));
 
@@ -1123,9 +1123,9 @@ void showCastDetails() {
 			// Move to the right of the Bitmap
 			ImGui::TableSetColumnIndex(1);
 
-			// Show Name of member
-			ImGui::Text(getDisplayName(member).c_str());
-			ImGui::Text(castType2str(memberType));
+			// the name is game data and can contain '%'
+			ImGui::TextUnformatted(getDisplayName(member).c_str());
+			ImGui::TextUnformatted(castType2str(memberType));
 
 			ImGui::EndTable();
 		}
diff --git a/engines/director/debugger/dt-controlpanel.cpp b/engines/director/debugger/dt-controlpanel.cpp
index 094dc8d97bc..d2fafe66d99 100644
--- a/engines/director/debugger/dt-controlpanel.cpp
+++ b/engines/director/debugger/dt-controlpanel.cpp
@@ -295,8 +295,8 @@ void showControlPanel() {
 
 		{
 			ImGui::Separator();
-			ImGui::TextColored(_state->theme->cp_path_color, movie->getArchive()->getPathName().toString().c_str());
-			ImGui::SetItemTooltip(movie->getArchive()->getPathName().toString().c_str());
+			ImGui::TextColored(_state->theme->cp_path_color, "%s", movie->getArchive()->getPathName().toString().c_str());
+			ImGui::SetItemTooltip("%s", movie->getArchive()->getPathName().toString().c_str());
 		}
 
 		ImGui::Separator();
diff --git a/engines/director/debugger/dt-save-state.cpp b/engines/director/debugger/dt-save-state.cpp
index 689977fd4f9..27a3504c2ad 100644
--- a/engines/director/debugger/dt-save-state.cpp
+++ b/engines/director/debugger/dt-save-state.cpp
@@ -183,7 +183,8 @@ void loadSavedState() {
 
 	_state->_logger->clear();
 	for (auto iter : log) {
-		_state->_logger->addLog(iter->asString().c_str());
+		// log lines can contain '%'
+		_state->_logger->addLog("%s", iter->asString().c_str());
 	}
 
 	// Load other settings


Commit: 51f73a6eee5401304696a30c37e16babd58f1964
    https://github.com/scummvm/scummvm/commit/51f73a6eee5401304696a30c37e16babd58f1964
Author: ramyak-sharma (ramyaksharma1 at gmail.com)
Date: 2026-07-11T01:17:05+02:00

Commit Message:
DIRECTOR: Return frame labels by value to fix leak in DT score window

Score::getFrameLabel() allocated a new Common::String on every call.
The DT score label bar calls it once per visible frame per rendered
frame and never freed the result, leaking continuously while the
Score window was open.

Returning by value removes the ownership hazard entirely. The only
other caller (kTheFrameLabel in lingo-the.cpp) wraps the result in a
heap string as the Datum contract requires.

Also fixes an off-by-one in the label bar: labels are stored with
1-based frame numbers, but the bar queried with the 0-based column
index, drawing every label one frame too early.

Changed paths:
    engines/director/debugger/dt-score.cpp
    engines/director/lingo/lingo-the.cpp
    engines/director/score.cpp
    engines/director/score.h


diff --git a/engines/director/debugger/dt-score.cpp b/engines/director/debugger/dt-score.cpp
index 7037bcafb20..5193323e2b8 100644
--- a/engines/director/debugger/dt-score.cpp
+++ b/engines/director/debugger/dt-score.cpp
@@ -1083,16 +1083,17 @@ static void drawLabelBar(ImDrawList *dl, ImVec2 pos, Score *score) {
 
 	for (int f = 0; f < cfg._visibleFrames; f++) {
 		int rf = startFrame + f;
-		Common::String *labelName = score->getFrameLabel((uint)rf);
+		// labels are stored with 1-based frame numbers, rf is 0-based
+		Common::String labelName = score->getFrameLabel((uint)rf + 1);
 		float x = cfg._cellWidth * (f + 0.5f) + pos.x;
 		float y = pos.y;
 
 		// Draw label triangle and add tooltip
-		if (labelName && !labelName->empty()) {
+		if (!labelName.empty()) {
 			float textY = y + (cfg._labelBarHeight - ImGui::GetTextLineHeight()) / 2.0f;
 
 			float iconW = ImGui::CalcTextSize(ICON_MS_BEENHERE).x;
-			float textW = ImGui::CalcTextSize(labelName->c_str()).x;
+			float textW = ImGui::CalcTextSize(labelName.c_str()).x;
 			float totalW = iconW + 2.0f + textW;
 
 			// draw background rect sized to icon + text
@@ -1106,12 +1107,12 @@ static void drawLabelBar(ImDrawList *dl, ImVec2 pos, Score *score) {
 			// draw text if it fits
 			float textX = x + 2.0f + iconW + 2.0f;
 			if (textX + textW < finalPos.x)
-				dl->AddText(ImVec2(textX, textY), _state->theme->sidebarTextColor, labelName->c_str());
+				dl->AddText(ImVec2(textX, textY), _state->theme->sidebarTextColor, labelName.c_str());
 
 			ImGui::SetCursorScreenPos(ImVec2(x, y));
 			ImGui::InvisibleButton(Common::String::format("##labelcell_%d", f).c_str(), ImVec2(cfg._cellWidth, cfg._labelBarHeight));
 			if (ImGui::IsItemHovered())
-				setTooltip("%s", labelName->c_str());
+				setTooltip("%s", labelName.c_str());
 
 		}
 	}
diff --git a/engines/director/lingo/lingo-the.cpp b/engines/director/lingo/lingo-the.cpp
index 0bb0f124334..08c2f195937 100644
--- a/engines/director/lingo/lingo-the.cpp
+++ b/engines/director/lingo/lingo-the.cpp
@@ -634,7 +634,7 @@ Datum Lingo::getTheEntity(int entity, Datum &id, int field) {
 		break;
 	case kTheFrameLabel:
 		d.type = STRING;
-		d.u.s = score->getFrameLabel(score->getCurrentFrameNum());
+		d.u.s = new Common::String(score->getFrameLabel(score->getCurrentFrameNum()));
 		break;
 	case kTheFramePalette:
 		d = score->getCurrentPalette().toMultiplex();
diff --git a/engines/director/score.cpp b/engines/director/score.cpp
index 758a60de77e..14c66573008 100644
--- a/engines/director/score.cpp
+++ b/engines/director/score.cpp
@@ -201,18 +201,16 @@ Common::String *Score::getLabelList() {
 	return res;
 }
 
-Common::String *Score::getFrameLabel(uint id) {
+Common::String Score::getFrameLabel(uint id) {
 	if (!_labels)
-		return new Common::String;
+		return Common::String();
 
 	for (auto &i : *_labels) {
-		if (i->number == id) {
-			return new Common::String(i->name);
-			break;
-		}
+		if (i->number == id)
+			return i->name;
 	}
 
-	return new Common::String;
+	return Common::String();
 }
 
 void Score::setStartToLabel(Common::String &label) {
diff --git a/engines/director/score.h b/engines/director/score.h
index edc1b710949..3abf9aba157 100644
--- a/engines/director/score.h
+++ b/engines/director/score.h
@@ -86,7 +86,7 @@ public:
 	static int compareLabels(const void *a, const void *b);
 	uint16 getLabel(Common::String &label);
 	Common::String *getLabelList();
-	Common::String *getFrameLabel(uint id);
+	Common::String getFrameLabel(uint id);
 	void setStartToLabel(Common::String &label);
 	void gotoLoop();
 	void gotoNext();


Commit: ac555dc66e36b4aefe6865b83fcb30b5305bf80f
    https://github.com/scummvm/scummvm/commit/ac555dc66e36b4aefe6865b83fcb30b5305bf80f
Author: ramyak-sharma (ramyaksharma1 at gmail.com)
Date: 2026-07-11T01:17:05+02:00

Commit Message:
DIRECTOR: DT: Guard Score and Channels windows against missing data

showScore() called buildContinuationData() before checking whether the
selected window has a movie loaded, dereferencing a null Movie when a
window is empty. showChannels() dereferenced Score::_currentFrame
without a null check, which crashes when the score has not started
playing yet.

The continuation data and sprite grid also indexed every frame's
sprite array with the channel count of frame 0. Frames are not
guaranteed to carry the same channel count, so clamp per frame and
treat a missing channel as an empty sprite. The sprite inspector now
bounds-checks its continuation data lookup as well, since the selected
channel/frame can outlive a rebuild of that table.

Also drops a duplicated pair of bounds checks in drawSpriteGrid that
was dead code after the identical pair a few lines up.

Changed paths:
    engines/director/debugger/dt-score.cpp


diff --git a/engines/director/debugger/dt-score.cpp b/engines/director/debugger/dt-score.cpp
index 5193323e2b8..34608a62eb6 100644
--- a/engines/director/debugger/dt-score.cpp
+++ b/engines/director/debugger/dt-score.cpp
@@ -162,10 +162,16 @@ static void buildContinuationData(Window *window) {
 		uint currentContinuation = 1;
 		for (int f = 0; f < (int)numFrames; f++) {
 			const Frame &frame = *score->_scoreCache[f];
+			// not every frame is guaranteed to carry the same channel count
+			if (ch >= (int)frame._sprites.size()) {
+				_state->_continuationData[ch][f].first = f;
+				currentContinuation = f;
+				continue;
+			}
 			Sprite &sprite = *frame._sprites[ch];
 
 			const Frame *prevFrame = (f == 0) ? nullptr : score->_scoreCache[f - 1];
-			Sprite *prevSprite = (prevFrame) ? prevFrame->_sprites[ch] : nullptr;
+			Sprite *prevSprite = (prevFrame && ch < (int)prevFrame->_sprites.size()) ? prevFrame->_sprites[ch] : nullptr;
 
 			if (prevSprite) {
 				if (!(*prevSprite == sprite)) {
@@ -200,10 +206,15 @@ static void buildContinuationData(Window *window) {
 		currentContinuation = 1;
 		for (int f = (int)numFrames - 1; f >= 0; f--) {
 			const Frame &frame = *score->_scoreCache[f];
+			if (ch >= (int)frame._sprites.size()) {
+				_state->_continuationData[ch][f].second = f;
+				currentContinuation = f;
+				continue;
+			}
 			Sprite &sprite = *frame._sprites[ch];
 
 			const Frame *nextFrame = (f == (int)numFrames - 1) ? nullptr : score->_scoreCache[f + 1];
-			Sprite *nextSprite = (nextFrame) ? nextFrame->_sprites[ch] : nullptr;
+			Sprite *nextSprite = (nextFrame && ch < (int)nextFrame->_sprites.size()) ? nextFrame->_sprites[ch] : nullptr;
 
 			if (nextSprite) {
 				if (!(*nextSprite == sprite)) {
@@ -468,7 +479,9 @@ static void drawSpriteInspector(Score *score, Cast *cast, uint numFrames) {
 		ImGui::PushStyleColor(ImGuiCol_ChildBg, ImGui::GetStyleColorVec4(ImGuiCol_FrameBg));
 		ImGui::BeginChild("Range", ImVec2(100.0f, 20.0f));
 
-		if (castMember || shape) {
+		if ((castMember || shape)
+				&& _state->_selectedScoreCast.channel < (int)_state->_continuationData.size()
+				&& _state->_selectedScoreCast.frame < (int)_state->_continuationData[_state->_selectedScoreCast.channel].size()) {
 			ImGui::TextUnformatted("\uf816"); ImGui::SameLine();	// line_start_circle
 			// the continuation data is 0-indexed but the frames are 1-indexed
 			ImGui::Text("%4d", _state->_continuationData[_state->_selectedScoreCast.channel][_state->_selectedScoreCast.frame].first + 1); ImGui::SameLine(50);
@@ -608,6 +621,7 @@ static void drawSpriteGrid(ImDrawList *dl, ImVec2 startPos, Score *score, Cast *
 			int rf = startFrame + f;
 			if (rf >= total_frames) break;
 			Frame &frame = *score->_scoreCache[rf];
+			if (ch >= (int)frame._sprites.size()) continue;
 			Sprite &sprite = *frame._sprites[ch];
 
 			if (!sprite._castId.member && !sprite.isQDShape()) continue;
@@ -621,9 +635,6 @@ static void drawSpriteGrid(ImDrawList *dl, ImVec2 startPos, Score *score, Cast *
 			float x1 = startPos.x + MAX<float>(spanStart - startFrame, 0) * cfg._cellWidth;
 			float x2 = MIN<float>(startPos.x + (spanEnd - startFrame + 1) * cfg._cellWidth, startPos.x + cfg._tableWidth);
 
-			if (ch >= (int)_state->_continuationData.size()) continue;
-			if (rf >= (int)_state->_continuationData[ch].size()) break;
-
 			bool startVisible = spanStart >= startFrame;
 			bool endVisible = spanEnd < startFrame + cfg._visibleFrames;
 
@@ -724,7 +735,8 @@ static void drawSpriteGrid(ImDrawList *dl, ImVec2 startPos, Score *score, Cast *
 			if (rf >= total_frames) break;
 
 			// recompute span info for tooltip
-			if (ch < (int)_state->_continuationData.size() && rf < (int)_state->_continuationData[ch].size()) {
+			if (ch < (int)_state->_continuationData.size() && rf < (int)_state->_continuationData[ch].size()
+					&& ch < (int)score->_scoreCache[rf]->_sprites.size()) {
 				Frame &frame = *score->_scoreCache[rf];
 				Sprite &sprite = *frame._sprites[ch];
 				int spanStart = _state->_continuationData[ch][rf].first;
@@ -1145,13 +1157,13 @@ void showScore() {
 	if (ImGui::Begin("Score", &_state->_w.score, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse)) {
 		Window *selectedWindow = windowListCombo(&_state->_scoreWindow);
 
-		buildContinuationData(selectedWindow);
-
 		if (!selectedWindow->getCurrentMovie()) {
 			ImGui::Text("No movie loaded");
 			ImGui::End();
 			return;
 		}
+
+		buildContinuationData(selectedWindow);
 		Score *score = selectedWindow->getCurrentMovie()->getScore();
 		uint numFrames = score->_scoreCache.size();
 		Cast *cast = selectedWindow->getCurrentMovie()->getCast();
@@ -1211,6 +1223,11 @@ void showChannels() {
 			return;
 		}
 		Score *score = selectedWindow->getCurrentMovie()->getScore();
+		if (!score->_currentFrame) {
+			ImGui::Text("No frame data");
+			ImGui::End();
+			return;
+		}
 		const Frame &frame = *score->_currentFrame;
 
 		CastMemberID defaultPalette = selectedWindow->getCurrentMovie()->_defaultPalette;


Commit: 38867cf706e44144451c293bf4b9d5c8c30a995c
    https://github.com/scummvm/scummvm/commit/38867cf706e44144451c293bf4b9d5c8c30a995c
Author: ramyak-sharma (ramyaksharma1 at gmail.com)
Date: 2026-07-11T01:17:05+02:00

Commit Message:
DIRECTOR: DT: Fix breakpoint toggles sharing IDs and use-after-erase

The breakpoint list created its per-row toggle button before the
row's PushID, so every row shared the same ImGui ID and clicking the
circle only ever toggled the first breakpoint. The D2 script renderer
had the same defect: its per-line breakpoint button had no ID scope
at all, making the click target work on just one line per window.

Both script renderers also kept a pointer into the breakpoints array
across g_lingo->delBreakpoint()/addBreakpoint() and then read
bp->enabled through it. delBreakpoint erases the element and
addBreakpoint can grow the array, so either way the pointer is
invalid at that point. The breakpoint state is now copied into locals
before the array can be mutated.

Changed paths:
    engines/director/debugger/dt-lists.cpp
    engines/director/debugger/dt-script-d2.cpp
    engines/director/debugger/dt-script-d4.cpp


diff --git a/engines/director/debugger/dt-lists.cpp b/engines/director/debugger/dt-lists.cpp
index 7208a81c51d..2c0fea41a74 100644
--- a/engines/director/debugger/dt-lists.cpp
+++ b/engines/director/debugger/dt-lists.cpp
@@ -217,6 +217,8 @@ void showBreakpointList() {
 				ImGui::TableNextRow();
 				ImGui::TableNextColumn();
 
+				ImGui::PushID(i);
+
 				ImDrawList *dl = ImGui::GetWindowDrawList();
 				ImVec2 pos = ImGui::GetCursorScreenPos();
 				const ImVec2 mid(pos.x + 7, pos.y + 7);
@@ -245,7 +247,6 @@ void showBreakpointList() {
 				// enabled column
 				ImGui::TableNextColumn();
 				PushStyleCompact();
-				ImGui::PushID(i);
 				ImGui::Checkbox("", &bps[i].enabled);
 				PopStyleCompact();
 
diff --git a/engines/director/debugger/dt-script-d2.cpp b/engines/director/debugger/dt-script-d2.cpp
index 6e732985fde..7b5721b0301 100644
--- a/engines/director/debugger/dt-script-d2.cpp
+++ b/engines/director/debugger/dt-script-d2.cpp
@@ -42,6 +42,7 @@ private:
 	bool _currentStatementDisplayed = false;
 	bool _scrollTo = false;
 	bool _scrollDone = true;
+	int _renderLineID = 1;
 
 public:
 	explicit RenderOldScriptVisitor(ImGuiScript &script, bool scrollTo) : _script(script), _scrollTo(scrollTo) {
@@ -804,17 +805,25 @@ private:
 		const float width = ImGui::GetContentRegionAvail().x;
 		const ImVec2 mid(pos.x + 7, pos.y + 7);
 
-		ImVec4 color = _state->theme->bp_color_disabled;
+		// add/delBreakpoint invalidate bp, so copy its state into locals
 		const Director::Breakpoint *bp = getBreakpoint(_script.handlerId, _script.id.member, pc);
-		if (bp)
-			color = _state->theme->bp_color_enabled;
+		bool hasBp = bp != nullptr;
+		bool bpEnabled = bp && bp->enabled;
+		ImVec4 color = hasBp ? _state->theme->bp_color_enabled : _state->theme->bp_color_disabled;
 
+		// Need to give a new id for each button
+		Common::String id = _script.handlerId + _renderLineID;
+		ImGui::PushID(id.c_str());
 		ImGui::InvisibleButton("Line", ImVec2(16, ImGui::GetFontSize()));
+		ImGui::PopID();
+		_renderLineID++;
 
 		// click on breakpoint column?
 		if (ImGui::IsItemClicked(0)) {
-			if (color == _state->theme->bp_color_enabled) {
+			if (hasBp) {
 				g_lingo->delBreakpoint(bp->id);
+				hasBp = false;
+				bpEnabled = false;
 				color = _state->theme->bp_color_disabled;
 			} else {
 				Director::Breakpoint newBp;
@@ -823,8 +832,11 @@ private:
 				newBp.funcName = _script.handlerId;
 				newBp.funcOffset = pc;
 				g_lingo->addBreakpoint(newBp);
+				hasBp = true;
+				bpEnabled = true;
 				color = _state->theme->bp_color_enabled;
 			}
+			bp = nullptr;
 		}
 
 		if (color == _state->theme->bp_color_disabled && ImGui::IsItemHovered()) {
@@ -832,7 +844,7 @@ private:
 		}
 
 		// draw breakpoint
-		if (!bp || bp->enabled)
+		if (!hasBp || bpEnabled)
 			dl->AddCircleFilled(mid, 4.0f, ImColor(color));
 		else
 			dl->AddCircle(mid, 4.0f, ImColor(_state->theme->line_color));
diff --git a/engines/director/debugger/dt-script-d4.cpp b/engines/director/debugger/dt-script-d4.cpp
index e47661be2a5..8786e8de9fc 100644
--- a/engines/director/debugger/dt-script-d4.cpp
+++ b/engines/director/debugger/dt-script-d4.cpp
@@ -1155,10 +1155,11 @@ private:
 		const float width = ImGui::GetContentRegionAvail().x;
 		const ImVec2 mid(pos.x + 7, pos.y + 7);
 
-		ImVec4 color = _state->theme->bp_color_disabled;
+		// add/delBreakpoint invalidate bp, so copy its state into locals
 		const Director::Breakpoint *bp = getBreakpoint(_script.handlerId, _script.id.member, pc);
-		if (bp)
-			color = _state->theme->bp_color_enabled;
+		bool hasBp = bp != nullptr;
+		bool bpEnabled = bp && bp->enabled;
+		ImVec4 color = hasBp ? _state->theme->bp_color_enabled : _state->theme->bp_color_disabled;
 
 		// Need to give a new id for each button
 		Common::String id = _script.handlerId + _renderLineID;
@@ -1169,8 +1170,10 @@ private:
 
 		// click on breakpoint column?
 		if (ImGui::IsItemClicked(0)) {
-			if (color == _state->theme->bp_color_enabled) {
+			if (hasBp) {
 				g_lingo->delBreakpoint(bp->id);
+				hasBp = false;
+				bpEnabled = false;
 				color = _state->theme->bp_color_disabled;
 			} else {
 				Director::Breakpoint newBp;
@@ -1179,8 +1182,11 @@ private:
 				newBp.funcName = _script.handlerId;
 				newBp.funcOffset = pc;
 				g_lingo->addBreakpoint(newBp);
+				hasBp = true;
+				bpEnabled = true;
 				color = _state->theme->bp_color_enabled;
 			}
+			bp = nullptr;
 		}
 
 		if (color == _state->theme->bp_color_disabled && ImGui::IsItemHovered()) {
@@ -1188,7 +1194,7 @@ private:
 		}
 
 		// draw breakpoint
-		if (!bp || bp->enabled)
+		if (!hasBp || bpEnabled)
 			dl->AddCircleFilled(mid, 4.0f, ImColor(color));
 		else
 			dl->AddCircle(mid, 4.0f, ImColor(_state->theme->line_color));


Commit: 184abcce34408d410cd8d6661b788b07c13fd304
    https://github.com/scummvm/scummvm/commit/184abcce34408d410cd8d6661b788b07c13fd304
Author: ramyak-sharma (ramyaksharma1 at gmail.com)
Date: 2026-07-11T01:17:05+02:00

Commit Message:
DIRECTOR: DT: Scope Cast window IDs per cast lib and show all casts

Cast window rows were identified only by the member number, which
repeats across cast libs and the shared cast. With a collision,
clicking a row could open the details of a different cast lib's
member. Both views now push the cast lib ID around their rows.

The type filter stopped at bit 14, so Xtra cast members (type 15)
were unconditionally filtered out even with "All" checked. The loop,
the "All" mask and the filter default now cover kCastXtra.

The grid view also skipped the shared cast that the list view
displays. The per-cast grid body is extracted into drawCastGrid() and
called for the shared cast too, mirroring the list view.

Changed paths:
    engines/director/debugger/dt-cast.cpp
    engines/director/debugger/dt-internal.h


diff --git a/engines/director/debugger/dt-cast.cpp b/engines/director/debugger/dt-cast.cpp
index 7ba4d2e04b4..ed9ced4dc13 100644
--- a/engines/director/debugger/dt-cast.cpp
+++ b/engines/director/debugger/dt-cast.cpp
@@ -113,6 +113,8 @@ Common::String getDisplayName(CastMember *castMember) {
 
 void drawCastRow(Cast* cast) {
 	assert(cast);
+	// member numbers repeat across cast libs, so scope the row IDs
+	ImGui::PushID(cast->_castLibID);
 	for (auto castMember : *cast->_loadedCast) {
 		castMember._value->load();
 
@@ -195,7 +197,89 @@ void drawCastRow(Cast* cast) {
 			break;
 		}
 	}
+	ImGui::PopID();
+}
+
+static void drawCastGrid(const Cast *cast, float thumbnailSize) {
+	ImGui::PushID(cast->_castLibID);
+	for (auto castMember : *cast->_loadedCast) {
+		castMember._value->load();
+
+		Common::String name(getDisplayName(castMember._value));
+		if (!_state->_cast._nameFilter.PassFilter(name.c_str()))
+			continue;
+		if ((castMember._value->_type != kCastTypeAny) && !(_state->_cast._typeFilter & (1 << (int)castMember._value->_type)))
+			continue;
+
+		ImGui::TableNextColumn();
+
+		ImGui::BeginGroup();
+		const ImVec2 textSize = ImGui::CalcTextSize(name.c_str());
+		float textWidth = textSize.x;
+		float textHeight = textSize.y;
+		if (textWidth > thumbnailSize) {
+			textWidth = thumbnailSize;
+			textHeight *= (textSize.x / textWidth);
+		}
+
+		ImGuiImage imgID = {};
+		switch (castMember._value->_type) {
+		case kCastBitmap:
+			{
+				imgID = getImageID(castMember._value);
+				if (imgID.id) {
+					showImage(imgID, name.c_str(), thumbnailSize);
+				}
+			}
+			break;
+
+		case kCastText:
+		case kCastRichText:
+		case kCastButton:
+			{
+				imgID = getTextID(castMember._value);
+				if (imgID.id) {
+					showImage(imgID, name.c_str(), thumbnailSize);
+				}
+			}
+			break;
 
+		case kCastShape:
+			{
+				imgID = getShapeID(castMember._value);
+				if (imgID.id) {
+					showImage(imgID, name.c_str(), thumbnailSize);
+				}
+			}
+			break;
+		default:
+			break;
+		}
+
+		if (!imgID.id) {
+			ImGui::PushID(castMember._key);
+			ImGui::InvisibleButton("##canvas", ImVec2(thumbnailSize, thumbnailSize));
+			ImGui::PopID();
+			const ImVec2 p0 = ImGui::GetItemRectMin();
+			const ImVec2 p1 = ImGui::GetItemRectMax();
+			ImGui::PushClipRect(p0, p1, true);
+			ImDrawList *draw_list = ImGui::GetWindowDrawList();
+			draw_list->AddRect(p0, p1, _state->theme->borderColor);
+			const ImVec2 pos = p0 + ImVec2((thumbnailSize - textWidth) * 0.5f, (thumbnailSize - textHeight) * 0.5f);
+			draw_list->AddText(nullptr, 0.f, pos, _state->theme->gridTextColor, name.c_str(), 0, thumbnailSize);
+			draw_list->AddText(nullptr, 0.f, p1 - ImVec2(16, 16), _state->theme->gridTextColor, toIcon(castMember._value->_type));
+			ImGui::PopClipRect();
+		}
+		ImGui::EndGroup();
+
+		if (ImGui::IsItemHovered() && ImGui::IsMouseClicked(0)) {
+			// Cast Member Clicked
+			castMember._value->load();
+			_state->_castDetails._castMemberID = CastMemberID(castMember._key, cast->_castLibID);
+			_state->_w.castDetails = true;
+		}
+	}
+	ImGui::PopID();
 }
 
 void showCast() {
@@ -222,9 +306,9 @@ void showCast() {
 		ImGui::SameLine();
 
 		if (ImGui::BeginPopup("filters_popup")) {
-			ImGui::CheckboxFlags("All", &_state->_cast._typeFilter, 0x7FFF);
+			ImGui::CheckboxFlags("All", &_state->_cast._typeFilter, 0xFFFF);
 			ImGui::Separator();
-			for (int i = 0; i <= 14; i++) {
+			for (int i = 0; i <= (int)kCastXtra; i++) {
 				ImGui::PushID(i);
 				Common::String option(Common::String::format("%s %s", toIcon((CastType)i), toString((CastType)i)));
 				ImGui::CheckboxFlags(option.c_str(), &_state->_cast._typeFilter, 1 << i);
@@ -272,85 +356,13 @@ void showCast() {
 					const Cast *cast = it._value;
 					if (!cast->_loadedCast)
 						continue;
-
-					for (auto castMember : *cast->_loadedCast) {
-						castMember._value->load();
-
-						Common::String name(getDisplayName(castMember._value));
-						if (!_state->_cast._nameFilter.PassFilter(name.c_str()))
-							continue;
-						if ((castMember._value->_type != kCastTypeAny) && !(_state->_cast._typeFilter & (1 << (int)castMember._value->_type)))
-							continue;
-
-						ImGui::TableNextColumn();
-
-						ImGui::BeginGroup();
-						const ImVec2 textSize = ImGui::CalcTextSize(name.c_str());
-						float textWidth = textSize.x;
-						float textHeight = textSize.y;
-						if (textWidth > thumbnailSize) {
-							textWidth = thumbnailSize;
-							textHeight *= (textSize.x / textWidth);
-						}
-
-						ImGuiImage imgID = {};
-						switch (castMember._value->_type) {
-						case kCastBitmap:
-							{
-								imgID = getImageID(castMember._value);
-								if (imgID.id) {
-									showImage(imgID, name.c_str(), thumbnailSize);
-								}
-							}
-							break;
-
-						case kCastText:
-						case kCastRichText:
-						case kCastButton:
-							{
-								imgID = getTextID(castMember._value);
-								if (imgID.id) {
-									showImage(imgID, name.c_str(), thumbnailSize);
-								}
-							}
-							break;
-
-						case kCastShape:
-							{
-								imgID = getShapeID(castMember._value);
-								if (imgID.id) {
-									showImage(imgID, name.c_str(), thumbnailSize);
-								}
-							}
-							break;
-						default:
-							break;
-						}
-
-						if (!imgID.id) {
-							ImGui::PushID(castMember._key);
-							ImGui::InvisibleButton("##canvas", ImVec2(thumbnailSize, thumbnailSize));
-							ImGui::PopID();
-							const ImVec2 p0 = ImGui::GetItemRectMin();
-							const ImVec2 p1 = ImGui::GetItemRectMax();
-							ImGui::PushClipRect(p0, p1, true);
-							ImDrawList *draw_list = ImGui::GetWindowDrawList();
-							draw_list->AddRect(p0, p1, _state->theme->borderColor);
-							const ImVec2 pos = p0 + ImVec2((thumbnailSize - textWidth) * 0.5f, (thumbnailSize - textHeight) * 0.5f);
-							draw_list->AddText(nullptr, 0.f, pos, _state->theme->gridTextColor, name.c_str(), 0, thumbnailSize);
-							draw_list->AddText(nullptr, 0.f, p1 - ImVec2(16, 16), _state->theme->gridTextColor, toIcon(castMember._value->_type));
-							ImGui::PopClipRect();
-						}
-						ImGui::EndGroup();
-
-						if (ImGui::IsItemHovered() && ImGui::IsMouseClicked(0)) {
-							// Cast Member Clicked
-							castMember._value->load();
-							_state->_castDetails._castMemberID = CastMemberID(castMember._key, cast->_castLibID);
-							_state->_w.castDetails = true;
-						}
-					}
+					drawCastGrid(cast, thumbnailSize);
 				}
+
+				const Cast *sharedCast = movie->getSharedCast();
+				if (sharedCast && sharedCast->_loadedCast)
+					drawCastGrid(sharedCast, thumbnailSize);
+
 				ImGui::EndTable();
 			}
 		}
diff --git a/engines/director/debugger/dt-internal.h b/engines/director/debugger/dt-internal.h
index efeb5053a2f..e7cfa46cdf8 100644
--- a/engines/director/debugger/dt-internal.h
+++ b/engines/director/debugger/dt-internal.h
@@ -220,7 +220,7 @@ typedef struct ImGuiState {
 		bool _listView = true;
 		int _thumbnailSize = 64;
 		ImGuiTextFilter _nameFilter;
-		int _typeFilter = 0x7FFF;
+		int _typeFilter = 0xFFFF;
 	} _cast;
 
 	struct {


Commit: 7900b36e7a4301179eb161075d8030c3d57883b6
    https://github.com/scummvm/scummvm/commit/7900b36e7a4301179eb161075d8030c3d57883b6
Author: ramyak-sharma (ramyaksharma1 at gmail.com)
Date: 2026-07-11T01:17:05+02:00

Commit Message:
DIRECTOR: DT: Harden script and resource lookups against bad input

Several debugger lookups crashed on data that is merely absent rather
than wrong:

- getScriptContext(), resolveHandlerContext() and
  buildImGuiHandlerScript() looked up cast libs with getVal(), which
  asserts on a missing key. A stack frame or stored reference naming
  a cast lib that no longer exists (e.g. after a movie switch) took
  the whole process down. They now use getValOrDefault() and fall
  into their existing null paths.
- toImGuiScript() read handler->script->propertyNames three lines
  before checking whether handler->script is null.
- getScriptContext(nameIndex, ...) indexed the archive name table
  without a bounds check, unlike its sibling resolveHandlerContext().
- The lookup helpers and windowListCombo() dereferenced
  getCurrentMovie() unconditionally; during a movie switch or for an
  empty window there is no movie.
- showFuncList() dereferenced context._value several times before its
  own null check, resolved child-script parents through the default
  cast instead of the cast owning the context, and the shared-cast
  branch passed the still-empty handlerName to formatHandlerName()
  where the regular branch passes handlerId.
- showArchive() assumed getResource() succeeds; a failed read now
  leaves the hex view empty instead of dereferencing null and showing
  freed memory.

Changed paths:
    engines/director/debugger/debugtools.cpp
    engines/director/debugger/dt-lists.cpp
    engines/director/debugger/dt-scripts.cpp


diff --git a/engines/director/debugger/debugtools.cpp b/engines/director/debugger/debugtools.cpp
index 938b7f9a5a9..15ff0acda0e 100644
--- a/engines/director/debugger/debugtools.cpp
+++ b/engines/director/debugger/debugtools.cpp
@@ -79,6 +79,8 @@ const LingoDec::Handler *getHandler(const Cast *cast, CastMemberID id, const Com
 
 const LingoDec::Handler *getHandler(CastMemberID id, const Common::String &handlerId) {
 	const Director::Movie *movie = g_director->getCurrentMovie();
+	if (!movie)
+		return nullptr;
 	if (id.castLib == SHARED_CAST_LIB)
 		return getHandler(movie->getSharedCast(), id, handlerId);
 
@@ -102,15 +104,18 @@ ImGuiScript toImGuiScript(ScriptType scriptType, CastMemberID id, const Common::
 
 	const LingoDec::Handler *handler = getHandler(id, handlerId);
 	if (!handler) {
+		Movie *movie = g_director->getCurrentMovie();
+		if (!movie)
+			return result;
 		const ScriptContext *ctx;
 		if (id.castLib == SHARED_CAST_LIB) {
 			// null guard
-			Cast *sharedCast = g_director->getCurrentMovie()->getSharedCast();
+			Cast *sharedCast = movie->getSharedCast();
 			if (!sharedCast)
 				return result;
 			ctx = sharedCast->_lingoArchive->getScriptContext(scriptType, id.member);
 		} else {
-			ctx = g_director->getCurrentMovie()->getScriptContext(scriptType, id);
+			ctx = movie->getScriptContext(scriptType, id);
 		}
 		if (!ctx) return result;
 		result.oldAst = ctx->_assemblyAST;
@@ -121,25 +126,27 @@ ImGuiScript toImGuiScript(ScriptType scriptType, CastMemberID id, const Common::
 	result.root = handler->ast.root;
 	result.isGenericEvent = handler->isGenericEvent;
 	result.argumentNames = handler->argumentNames;
-	result.propertyNames = handler->script->propertyNames;
 	result.globalNames = handler->globalNames;
 
 	LingoDec::Script *script = handler->script;
 	if (!script)
 		return result;
 
+	result.propertyNames = script->propertyNames;
 	result.isMethod = script->isFactory();
 	return result;
 }
 
 ScriptContext *getScriptContext(CastMemberID id) {
 	const Director::Movie *movie = g_director->getCurrentMovie();
+	if (!movie)
+		return nullptr;
 	const Cast *cast;
 
 	if (id.castLib == SHARED_CAST_LIB)
 		cast = movie->getSharedCast();
 	else
-		cast = movie->getCasts()->getVal(id.castLib);
+		cast = movie->getCasts()->getValOrDefault(id.castLib, nullptr);
 
 	if (!cast)
 		return nullptr;
@@ -159,6 +166,8 @@ ScriptContext *getScriptContext(CastMemberID id) {
 
 ScriptContext *getScriptContext(uint32 nameIndex, CastMemberID id, Common::String handlerName) {
 	Movie *movie = g_director->getCurrentMovie();
+	if (!movie)
+		return nullptr;
 	Cast *cast;
 	if (id.castLib == SHARED_CAST_LIB)
 		cast = movie->getSharedCast();
@@ -169,7 +178,7 @@ ScriptContext *getScriptContext(uint32 nameIndex, CastMemberID id, Common::Strin
 		return nullptr;
 
 	// If the name at nameIndex is not the same as handler name, means its a local script (in the same Lscr resource)
-	if (cast->_lingoArchive->names[nameIndex] != handlerName) {
+	if (nameIndex < cast->_lingoArchive->names.size() && cast->_lingoArchive->names[nameIndex] != handlerName) {
 		return cast->_lingoArchive->findScriptContext(id.member);
 	}
 
@@ -206,7 +215,7 @@ ScriptContext *resolveHandlerContext(int32 nameIndex, const CastMemberID &refId,
 	if (refId.castLib == SHARED_CAST_LIB) {
 		cast = movie->getSharedCast();
 	} else {
-		cast = movie->getCasts()->getVal(refId.castLib);
+		cast = movie->getCasts()->getValOrDefault(refId.castLib, nullptr);
 	}
 
 	if (cast && cast->_lingoArchive && nameIndex >= 0 && (uint32)nameIndex < cast->_lingoArchive->names.size()) {
@@ -239,7 +248,7 @@ ImGuiScript buildImGuiHandlerScript(ScriptContext *ctx, int castLibID, const Com
 	if (castLibID == SHARED_CAST_LIB) {
 		cast = movie ? movie->getSharedCast() : nullptr;
 	} else {
-		cast = movie ? movie->getCasts()->getVal(castLibID) : nullptr;
+		cast = movie ? movie->getCasts()->getValOrDefault(castLibID, nullptr) : nullptr;
 	}
 
 	int castId = ctx->_id;
@@ -611,12 +620,14 @@ Window *windowListCombo(Common::String *target) {
 	const Common::String selWin = *target;
 	Window *res = nullptr;
 
-	Common::String stage = g_director->getStage()->getCurrentMovie()->getMacName();
+	// windows may not have a movie loaded yet
+	Movie *stageMovie = g_director->getStage()->getCurrentMovie();
+	Common::String stage = stageMovie ? stageMovie->getMacName() : Common::String();
 
 	// Check if the relevant window is gone
 	bool found = false;
 	for (auto window : (*windowList)) {
-		if (window->getCurrentMovie()->getMacName() == selWin) {
+		if (window->getCurrentMovie() && window->getCurrentMovie()->getMacName() == selWin) {
 			// Found the selected window
 			found = true;
 			res = window;
@@ -644,6 +655,8 @@ Window *windowListCombo(Common::String *target) {
 		}
 
 		for (auto window : (*windowList)) {
+			if (!window->getCurrentMovie())
+				continue;
 			Common::String winName = window->getCurrentMovie()->getMacName();
 			selected = (*target == winName);
 			if (ImGui::Selectable(winName.c_str(), selected)) {
diff --git a/engines/director/debugger/dt-lists.cpp b/engines/director/debugger/dt-lists.cpp
index 2c0fea41a74..3bf2ac026f5 100644
--- a/engines/director/debugger/dt-lists.cpp
+++ b/engines/director/debugger/dt-lists.cpp
@@ -322,13 +322,17 @@ void showArchive() {
 									_state->_archive.resId = id;
 
 									free(_state->_archive.data);
+									_state->_archive.data = nullptr;
+									_state->_archive.dataSize = 0;
 
 									Common::SeekableReadStreamEndian *res = archive->getResource(tag, id);
-									_state->_archive.data = (byte *)malloc(res->size());
-									res->read(_state->_archive.data, res->size());
-									_state->_archive.dataSize = res->size();
+									if (res) {
+										_state->_archive.data = (byte *)malloc(res->size());
+										res->read(_state->_archive.data, res->size());
+										_state->_archive.dataSize = res->size();
 
-									delete res;
+										delete res;
+									}
 								}
 							}
 
diff --git a/engines/director/debugger/dt-scripts.cpp b/engines/director/debugger/dt-scripts.cpp
index bb87930d09a..c48cc59986d 100644
--- a/engines/director/debugger/dt-scripts.cpp
+++ b/engines/director/debugger/dt-scripts.cpp
@@ -288,6 +288,9 @@ void showFuncList() {
 
 				if (ImGui::TreeNodeEx(castName.c_str(), ImGuiTreeNodeFlags_DefaultOpen)) {
 					for (auto context : cast._value->_lingoArchive->lctxContexts) {
+						if (!context._value || !context._value->_functionHandlers.size()) {
+							continue;
+						}
 						CastMemberInfo *cmi = cast._value->getCastMemberInfo(context._value->_id);
 						Common::String contextName = Common::String::format("%d", context._value->_id);
 						if (cmi && cmi->name.size()) {
@@ -295,7 +298,7 @@ void showFuncList() {
 						}
 
 						contextName += Common::String::format(": %s", scriptType2str(context._value->_scriptType));
-						if (!context._value || !context._value->_functionHandlers.size() || !_state->_functions._nameFilter.PassFilter(contextName.c_str())) {
+						if (!_state->_functions._nameFilter.PassFilter(contextName.c_str())) {
 							continue;
 						}
 
@@ -306,7 +309,8 @@ void showFuncList() {
 								int castId = context._value->_id;
 								bool childScript = false;
 								if (castId == -1) {
-									castId = movie->getCast()->getCastIdByScriptId(context._value->_parentNumber);
+									// resolve the parent script in the cast that owns this context
+									castId = cast._value->getCastIdByScriptId(context._value->_parentNumber);
 									childScript = true;
 								}
 
@@ -343,6 +347,9 @@ void showFuncList() {
 
 				if (ImGui::TreeNode(castName.c_str())) {
 					for (auto context : sharedCast->_lingoArchive->lctxContexts) {
+						if (!context._value || !context._value->_functionHandlers.size()) {
+							continue;
+						}
 						CastMemberInfo *cmi = sharedCast->getCastMemberInfo(context._value->_id);
 						Common::String contextName = Common::String::format("%d", context._value->_id);
 						if (cmi && cmi->name.size()) {
@@ -350,7 +357,7 @@ void showFuncList() {
 						}
 
 						contextName += Common::String::format(": %s", scriptType2str(context._value->_scriptType));
-						if (!context._value || !context._value->_functionHandlers.size() || !_state->_functions._nameFilter.PassFilter(contextName.c_str())) {
+						if (!_state->_functions._nameFilter.PassFilter(contextName.c_str())) {
 							continue;
 						}
 
@@ -361,7 +368,8 @@ void showFuncList() {
 								int castId = context._value->_id;
 								bool childScript = false;
 								if (castId == -1) {
-									castId = movie->getCast()->getCastIdByScriptId(context._value->_parentNumber);
+									// resolve the parent script in the cast that owns this context
+									castId = sharedCast->getCastIdByScriptId(context._value->_parentNumber);
 									childScript = true;
 								}
 
@@ -375,7 +383,7 @@ void showFuncList() {
 										ImGuiScript script = toImGuiScript(context._value->_scriptType, memberID, functionHandler._key);
 										script.byteOffsets = context._value->_functionByteOffsets[script.handlerId];
 										script.moviePath = movie->getArchive()->getPathName().toString();
-										script.handlerName = formatHandlerName(context._value->_scriptId, castId, script.handlerName, context._value->_scriptType, childScript);
+										script.handlerName = formatHandlerName(context._value->_scriptId, castId, script.handlerId, context._value->_scriptType, childScript);
 										addToOpenHandlers(script);
 									}
 								}


Commit: 7a9c0b1d7c4f228c2752d0ed8f249f4f6d6ce712
    https://github.com/scummvm/scummvm/commit/7a9c0b1d7c4f228c2752d0ed8f249f4f6d6ce712
Author: ramyak-sharma (ramyaksharma1 at gmail.com)
Date: 2026-07-11T01:17:05+02:00

Commit Message:
DIRECTOR: DT: Drop CastMember-keyed caches when a movie changes

The thumbnail texture cache is keyed by raw CastMember pointers and
was never invalidated. After a movie switch the entries keep dangling
keys; if a new cast member is allocated at a recycled address, the
lookup silently returns another movie's texture, and the GPU textures
of the old movie leak. The film loop frame positions had the same
lifetime problem.

onImGuiRender() now tracks a signature built from the archive paths
of every window's movie (stage and windowed movies alike, so movies
in a window are covered too) and frees the textures plus the film
loop positions whenever it changes.

onImGuiCleanup() now also releases the cached textures and the image
viewer's text buffer, which were leaked on shutdown.

Changed paths:
    engines/director/debugger/debugtools.cpp
    engines/director/debugger/dt-internal.h


diff --git a/engines/director/debugger/debugtools.cpp b/engines/director/debugger/debugtools.cpp
index 15ff0acda0e..94587386f07 100644
--- a/engines/director/debugger/debugtools.cpp
+++ b/engines/director/debugger/debugtools.cpp
@@ -909,6 +909,30 @@ void onImGuiInit() {
 	Common::setLogWatcher(onLog);
 }
 
+// Caches keyed by CastMember pointers dangle when any window switches movies.
+static void invalidateStaleCaches() {
+	Common::String signature;
+	Movie *stageMovie = g_director->getStage()->getCurrentMovie();
+	if (stageMovie)
+		signature = stageMovie->getArchive()->getPathName().toString();
+	for (auto window : *g_director->getWindowList()) {
+		Movie *movie = window->getCurrentMovie();
+		if (movie) {
+			signature += '|';
+			signature += movie->getArchive()->getPathName().toString();
+		}
+	}
+
+	if (signature == _state->_movieSignature)
+		return;
+	_state->_movieSignature = signature;
+
+	for (auto &it : _state->_cast._textures)
+		g_system->freeImGuiTexture((void *)(intptr_t)it._value.id);
+	_state->_cast._textures.clear();
+	_state->_castDetails._filmLoopCurrentFrame.clear();
+}
+
 void onImGuiRender() {
 	if (!debugChannelSet(-1, kDebugImGui)) {
 		ImGui::GetIO().ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange | ImGuiConfigFlags_NoMouse;
@@ -918,6 +942,8 @@ void onImGuiRender() {
 	if (!_state)
 		return;
 
+	invalidateStaleCaches();
+
 	if (_state->_windowToRedraw) {
 		_state->_windowToRedraw->render(true);
 		_state->_windowToRedraw = nullptr;
@@ -1015,6 +1041,10 @@ void onImGuiCleanup() {
 	Common::setLogWatcher(nullptr);
 	if (_state) {
 		free(_state->_archive.data);
+		free(_state->_imageViewerState.buffer);
+
+		for (auto &it : _state->_cast._textures)
+			g_system->freeImGuiTexture((void *)(intptr_t)it._value.id);
 
 		delete _state->_logger;
 	}
diff --git a/engines/director/debugger/dt-internal.h b/engines/director/debugger/dt-internal.h
index e7cfa46cdf8..7ffbc883cad 100644
--- a/engines/director/debugger/dt-internal.h
+++ b/engines/director/debugger/dt-internal.h
@@ -299,6 +299,9 @@ typedef struct ImGuiState {
 	Common::Array<Common::Array<Common::Pair<uint, uint>>> _continuationData;
 	Common::String _loadedContinuationData;
 
+	// archive paths of every window's movie, to detect movie switches
+	Common::String _movieSignature;
+
 	Common::Array<WatchLogEntry> _watchLog;
 
 	Common::String _scoreWindow;


Commit: 695080e8c1275be3149e520a265d4a92cc4fa358
    https://github.com/scummvm/scummvm/commit/695080e8c1275be3149e520a265d4a92cc4fa358
Author: ramyak-sharma (ramyaksharma1 at gmail.com)
Date: 2026-07-11T01:17:05+02:00

Commit Message:
DIRECTOR: DT: Fix window selector wiring and small logic slips

- The Channels window shared the Score window's selector variable, so
  picking a window in one silently changed the other. It now uses the
  _channelsWindow slot that was already saved and restored by the
  save-state code but never wired up.
- loadSavedState() had a leftover line that overwrote the Archive
  window flag with bit 0 of the flags word (which belongs to another
  window), after the loop above had already restored every flag.
- The control panel treated getFramesNum() - 1 as the last frame,
  but frame numbers are 1-based: the true last frame was unreachable
  by stepping forward, the frame input clamp, and the step-back
  wraparound.
- displayVariable() derived the watched state from color equality
  comparisons; a theme where those colors coincide would break the
  toggle. It now keeps the state in an explicit bool.

Changed paths:
    engines/director/debugger/debugtools.cpp
    engines/director/debugger/dt-controlpanel.cpp
    engines/director/debugger/dt-save-state.cpp
    engines/director/debugger/dt-score.cpp


diff --git a/engines/director/debugger/debugtools.cpp b/engines/director/debugger/debugtools.cpp
index 94587386f07..49784033969 100644
--- a/engines/director/debugger/debugtools.cpp
+++ b/engines/director/debugger/debugtools.cpp
@@ -492,12 +492,8 @@ ImGuiImage getTextID(CastMember *castMember) {
 
 void displayVariable(const Common::String &name, bool changed, bool outOfScope) {
 	ImU32 var_color = ImGui::GetColorU32(_state->theme->var_ref);
-	ImU32 color;
-
-	color = ImGui::GetColorU32(_state->theme->bp_color_disabled);
 
-	if (_state->_variables.contains(name))
-		color = ImGui::GetColorU32(_state->theme->bp_color_enabled);
+	bool watched = _state->_variables.contains(name);
 
 	ImDrawList *dl = ImGui::GetWindowDrawList();
 	ImVec2 pos = ImGui::GetCursorScreenPos();
@@ -506,12 +502,12 @@ void displayVariable(const Common::String &name, bool changed, bool outOfScope)
 
 	ImGui::InvisibleButton("Line", ImVec2(textSize.x + eyeSize.x, textSize.y));
 	if (ImGui::IsItemClicked(0)) {
-		if (color == ImGui::GetColorU32(_state->theme->bp_color_enabled)) {
+		if (watched) {
 			_state->_variables.erase(name);
-			color = ImGui::GetColorU32(_state->theme->bp_color_disabled);
+			watched = false;
 		} else {
 			_state->_variables[name] = true;
-			color = ImGui::GetColorU32(_state->theme->bp_color_enabled);
+			watched = true;
 		}
 	}
 
@@ -521,9 +517,13 @@ void displayVariable(const Common::String &name, bool changed, bool outOfScope)
 		var_color = ImGui::GetColorU32(_state->theme->var_ref_out_of_scope);
 	}
 
-	if (color == ImGui::GetColorU32(_state->theme->bp_color_disabled) && ImGui::IsItemHovered()) {
+	ImU32 color;
+	if (watched)
+		color = ImGui::GetColorU32(_state->theme->bp_color_enabled);
+	else if (ImGui::IsItemHovered())
 		color = ImGui::GetColorU32(_state->theme->bp_color_hover);
-	}
+	else
+		color = ImGui::GetColorU32(_state->theme->bp_color_disabled);
 
 	dl->AddText(pos, color, ICON_MS_VISIBILITY " ");
 	dl->AddText(ImVec2(pos.x + eyeSize.x, pos.y), var_color, name.c_str());
diff --git a/engines/director/debugger/dt-controlpanel.cpp b/engines/director/debugger/dt-controlpanel.cpp
index d2fafe66d99..cecc946c5ad 100644
--- a/engines/director/debugger/dt-controlpanel.cpp
+++ b/engines/director/debugger/dt-controlpanel.cpp
@@ -146,7 +146,8 @@ void showControlPanel() {
 		float bgX1 = -4.0f, bgX2 = 21.0f;
 
 		int frameNum = score->getCurrentFrameNum();
-		int maxFrame = score->getFramesNum() - 1;
+		// frame numbers are 1-based
+		int maxFrame = score->getFramesNum();
 
 		if (_state->_prevFrame != -1 && _state->_prevFrame != frameNum) {
 			score->_playState = kPlayPaused;
diff --git a/engines/director/debugger/dt-save-state.cpp b/engines/director/debugger/dt-save-state.cpp
index 27a3504c2ad..1e49c762a49 100644
--- a/engines/director/debugger/dt-save-state.cpp
+++ b/engines/director/debugger/dt-save-state.cpp
@@ -159,7 +159,6 @@ void loadSavedState() {
 		*it.flag = (openFlags & 1 << index) ? true : false;
 		index += 1;
 	}
-	_state->_w.archive = (openFlags & 1) ? true : false;
 	if (debugChannelSet(7, kDebugImGui)) {
 		debugC(7, kDebugImGui, "Window flags: ");
 		for (auto it : windows) {
diff --git a/engines/director/debugger/dt-score.cpp b/engines/director/debugger/dt-score.cpp
index 34608a62eb6..232a034f4ae 100644
--- a/engines/director/debugger/dt-score.cpp
+++ b/engines/director/debugger/dt-score.cpp
@@ -1215,7 +1215,7 @@ void showChannels() {
 	ImGui::SetNextWindowSize(windowSize, ImGuiCond_FirstUseEver);
 
 	if (ImGui::Begin("Channels", &_state->_w.channels)) {
-		Window *selectedWindow = windowListCombo(&_state->_scoreWindow);
+		Window *selectedWindow = windowListCombo(&_state->_channelsWindow);
 
 		if (!selectedWindow->getCurrentMovie()) {
 			ImGui::Text("No movie loaded");


Commit: 20f41e82b765e38f955846c782eef38ee2f42dce
    https://github.com/scummvm/scummvm/commit/20f41e82b765e38f955846c782eef38ee2f42dce
Author: ramyak-sharma (ramyaksharma1 at gmail.com)
Date: 2026-07-11T01:17:05+02:00

Commit Message:
DIRECTOR: DT: Fix script navigation, highlighting and view controls

- Clicking a handler in the Functions list or Script window did not
  scroll to it: the global go-to-definition flag was consumed by
  whichever list rendered first in the frame. Each script list now
  carries its own scroll request flag, and go-to-definition opens the
  handler in the window that hosts the click instead of always the
  Execution Context.
- The current statement highlight and byte offsets appeared only on
  stack frames whose script lives in the default cast lib, because
  backtrace rendering assumed the default lib for every frame. The
  cast lib is now resolved by finding which cast's LingoArchive owns
  the frame's script context, and the Execution Context renders
  scripts with the same code path as the Scripts window, including
  breakpoints.
- The Execution Context and Functions list had no way to switch
  between the stage and movie-in-a-window movies. Both now use the
  same window selector combo as the Cast window.
- The paired view toggles (Lingo/Bytecode, List/Grid, Contexts/
  Handlers) were two independent toggle buttons acting like a broken
  radio group: switching views took two clicks and clicking the
  active button flipped both. They are now proper selector buttons:
  clicking a button selects its view, clicking the active one is a
  no-op.

Changed paths:
    engines/director/debugger/debugtools.cpp
    engines/director/debugger/dt-cast.cpp
    engines/director/debugger/dt-internal.h
    engines/director/debugger/dt-script-d2.cpp
    engines/director/debugger/dt-script-d4.cpp
    engines/director/debugger/dt-scripts.cpp
    engines/director/debugger/dt-search.cpp


diff --git a/engines/director/debugger/debugtools.cpp b/engines/director/debugger/debugtools.cpp
index 49784033969..9599022ca7d 100644
--- a/engines/director/debugger/debugtools.cpp
+++ b/engines/director/debugger/debugtools.cpp
@@ -191,6 +191,45 @@ ScriptContext *getScriptContext(uint32 nameIndex, CastMemberID id, Common::Strin
 	return nullptr;
 }
 
+static bool archiveOwnsContext(LingoArchive *archive, const ScriptContext *ctx) {
+	if (!archive)
+		return false;
+	for (int i = 0; i <= kMaxScriptType; i++) {
+		for (auto &it : archive->scriptContexts[i])
+			if (it._value == ctx)
+				return true;
+	}
+	for (auto &it : archive->lctxContexts)
+		if (it._value == ctx)
+			return true;
+	for (auto &factory : archive->factoryContexts) {
+		if (!factory._value)
+			continue;
+		for (auto &it : *factory._value)
+			if (it._value == ctx)
+				return true;
+	}
+	return false;
+}
+
+// Find the cast library (or shared cast) a script context belongs to.
+int getCastLibIDForContext(const ScriptContext *ctx) {
+	Movie *movie = g_director->getCurrentMovie();
+	if (!movie || !ctx)
+		return DEFAULT_CAST_LIB;
+
+	for (auto &it : *movie->getCasts()) {
+		if (it._value && archiveOwnsContext(it._value->_lingoArchive, ctx))
+			return it._key;
+	}
+
+	Cast *shared = movie->getSharedCast();
+	if (shared && archiveOwnsContext(shared->_lingoArchive, ctx))
+		return SHARED_CAST_LIB;
+
+	return movie->getCast() ? movie->getCast()->_castLibID : DEFAULT_CAST_LIB;
+}
+
 static ScriptContext *findHandlerContext(Cast *cast, const Common::String &handlerName) {
 	if (!cast || !cast->_lingoArchive)
 		return nullptr;
@@ -559,6 +598,7 @@ void addToOpenHandlers(ImGuiScript handler) {
 
 	ScriptData &data = _state->_openScripts;
 	_state->_w.scripts = true;  // always (re)open the window
+	data._scrollToCurrent = true;
 	// Truncate forward history when navigating to a new script
 	if (data._current + 1 < data._scripts.size())
 		data._scripts.resize(data._current + 1);
@@ -573,6 +613,7 @@ void addToOpenHandlers(ImGuiScript handler) {
 void setScriptToDisplay(const ImGuiScript &script) {
 	ScriptData *scriptData = &_state->_functions._windowScriptData.getOrCreateVal(g_director->getCurrentWindow());
 	uint index = scriptData->_scripts.size();
+	scriptData->_scrollToCurrent = true;
 	if (index && scriptData->_scripts[index - 1] == script) {
 		scriptData->_showScript = true;
 		return;
@@ -607,6 +648,20 @@ void displayScriptRef(CastMemberID &scriptId) {
 	}
 }
 
+// One button of a mutually exclusive pair: clicking selects it, the
+// already-selected button is inert (unlike ImGuiEx::toggleButton).
+bool selectableViewButton(const char *label, bool selected) {
+	int pop = 0;
+	if (selected) {
+		ImVec4 hovered = ImGui::GetStyle().Colors[ImGuiCol_ButtonHovered];
+		ImGui::PushStyleColor(ImGuiCol_Button, hovered);
+		pop = 1;
+	}
+	bool clicked = ImGui::Button(label);
+	ImGui::PopStyleColor(pop);
+	return clicked;
+}
+
 ImColor brightenColor(const ImColor& color, float factor) {
 	ImVec4 col = color.Value;
 	col.x = CLIP<float>(col.x * factor, 0.0f, 1.0f);
diff --git a/engines/director/debugger/dt-cast.cpp b/engines/director/debugger/dt-cast.cpp
index ed9ced4dc13..08ca3d61898 100644
--- a/engines/director/debugger/dt-cast.cpp
+++ b/engines/director/debugger/dt-cast.cpp
@@ -293,10 +293,12 @@ void showCast() {
 		Window *selectedWindow = windowListCombo(&_state->_castWindow);
 
 		// display a toolbar with: grid/list/filters buttons + name filter
-		ImGuiEx::toggleButton(ICON_MS_LIST, &_state->_cast._listView);
+		if (selectableViewButton(ICON_MS_LIST, _state->_cast._listView))
+			_state->_cast._listView = true;
 		ImGui::SetItemTooltip("List");
 		ImGui::SameLine();
-		ImGuiEx::toggleButton(ICON_MS_GRID_VIEW, &_state->_cast._listView, true);
+		if (selectableViewButton(ICON_MS_GRID_VIEW, !_state->_cast._listView))
+			_state->_cast._listView = false;
 		ImGui::SetItemTooltip("Grid");
 		ImGui::SameLine();
 
diff --git a/engines/director/debugger/dt-internal.h b/engines/director/debugger/dt-internal.h
index 7ffbc883cad..3b7f7d22086 100644
--- a/engines/director/debugger/dt-internal.h
+++ b/engines/director/debugger/dt-internal.h
@@ -118,6 +118,7 @@ typedef struct ScriptData {
 	uint _current = 0;
 	bool _showByteCode = false;
 	bool _showScript = false;
+	bool _scrollToCurrent = false; // pending scroll to the current script, consumed on render
 } ScriptData;
 
 typedef struct WindowFlag {
@@ -236,7 +237,7 @@ typedef struct ImGuiState {
 
 	struct {
 		bool _isScriptDirty = false; // indicates whether or not we have to display the script corresponding to the current stackframe
-		bool _goToDefinition = false;
+		bool _hostExecutionContext = false; // true while the Execution Context window is rendering scripts
 		bool _scrollToPC = false;
 		uint _lastLinePC = 0;
 		uint _callstackSize = 0;
@@ -307,6 +308,8 @@ typedef struct ImGuiState {
 	Common::String _scoreWindow;
 	Common::String _channelsWindow;
 	Common::String _castWindow;
+	Common::String _functionsWindow;
+	Common::String _executionContextWindow;
 	int _scoreMode = 0;
 	int _scoreFrameOffset = 1;
 	int _scorePageSlider = 0;
@@ -342,6 +345,7 @@ ImGuiScript toImGuiScript(ScriptType scriptType, CastMemberID id, const Common::
 ScriptContext *getScriptContext(CastMemberID id);
 ScriptContext *getScriptContext(uint32 nameIndex, CastMemberID castId, Common::String handler);
 ScriptContext *resolveHandlerContext(int32 nameIndex, const CastMemberID &refId, const Common::String &handlerName);
+int getCastLibIDForContext(const ScriptContext *ctx);
 ImGuiScript buildImGuiHandlerScript(ScriptContext *ctx, int castLibID, const Common::String &handlerName, const Common::String &moviePath);
 void maybeHighlightLastItem(const Common::String &text);
 void addToOpenHandlers(ImGuiScript handler);
@@ -358,6 +362,7 @@ ImVec4 convertColor(uint32 color);
 void displayVariable(const Common::String &name, bool changed, bool outOfScope = false);
 ImColor brightenColor(const ImColor &color, float factor);
 Window *windowListCombo(Common::String *target);
+bool selectableViewButton(const char *label, bool selected);
 Common::String formatHandlerName(int scriptId, int castId, Common::String handlerName, ScriptType scriptType, bool childScript);
 void setTheme(int themeIndex);
 void openImageViewer(ImGuiImage image, const Common::String &text = "", const Common::String &title = "");
diff --git a/engines/director/debugger/dt-script-d2.cpp b/engines/director/debugger/dt-script-d2.cpp
index 7b5721b0301..fe15024e6df 100644
--- a/engines/director/debugger/dt-script-d2.cpp
+++ b/engines/director/debugger/dt-script-d2.cpp
@@ -74,9 +74,8 @@ public:
 			}
 			ImGui::NewLine();
 		}
-		if (_state->_dbg._goToDefinition && _scrollTo) {
+		if (_scrollTo) {
 			ImGui::SetScrollHereY(0.5f);
-			_state->_dbg._goToDefinition = false;
 		}
 
 		indent();
@@ -511,8 +510,11 @@ public:
 			ScriptContext *context = resolveHandlerContext(obj, _script.id, *node->name);
 			if (context) {
 				ImGuiScript script = buildImGuiHandlerScript(context, _script.id.castLib, *node->name, _script.moviePath);
-				setScriptToDisplay(script);
-				_state->_dbg._goToDefinition = true;
+				// Open the definition in the window that hosts this script view
+				if (_state->_dbg._hostExecutionContext)
+					setScriptToDisplay(script);
+				else
+					addToOpenHandlers(script);
 			}
 		}
 		ImGui::SameLine();
diff --git a/engines/director/debugger/dt-script-d4.cpp b/engines/director/debugger/dt-script-d4.cpp
index 8786e8de9fc..f621236ca29 100644
--- a/engines/director/debugger/dt-script-d4.cpp
+++ b/engines/director/debugger/dt-script-d4.cpp
@@ -127,8 +127,11 @@ public:
 			ScriptContext *context = resolveHandlerContext(obj, _script.id, node.name);
 			if (context) {
 				ImGuiScript script = buildImGuiHandlerScript(context, _script.id.castLib, node.name, _script.moviePath);
-				setScriptToDisplay(script);
-				_state->_dbg._goToDefinition = true;
+				// Open the definition in the window that hosts this script view
+				if (_state->_dbg._hostExecutionContext)
+					setScriptToDisplay(script);
+				else
+					addToOpenHandlers(script);
 			}
 		}
 		ImGui::SameLine();
@@ -996,9 +999,8 @@ private:
 				ImGui::SameLine();
 			}
 		}
-		if (_state->_dbg._goToDefinition && _scrollTo) {
+		if (_scrollTo) {
 			ImGui::SetScrollHereY(0.5f);
-			_state->_dbg._goToDefinition = false;
 		}
 
 		indent();
diff --git a/engines/director/debugger/dt-scripts.cpp b/engines/director/debugger/dt-scripts.cpp
index c48cc59986d..cdf05b21ad2 100644
--- a/engines/director/debugger/dt-scripts.cpp
+++ b/engines/director/debugger/dt-scripts.cpp
@@ -59,6 +59,8 @@ static void renderCallStack(uint pc) {
 	}
 
 	const Movie *movie = g_director->getCurrentMovie();
+	if (!movie)
+		return;
 
 	ImGui::Text("Call stack:\n");
 	for (int i = 0; i < (int)callstack.size(); i++) {
@@ -76,7 +78,11 @@ static void renderCallStack(uint pc) {
 			if (frame->sp.ctx && frame->sp.ctx->_id != -1) {
 				stackFrame += Common::String::format("(%d): ", frame->sp.ctx->_id);
 			} else if (frame->sp.ctx) {
-				stackFrame += Common::String::format("(<p%d>): ", movie->getCast()->getCastIdByScriptId(frame->sp.ctx->_parentNumber));
+				// resolve in the cast lib that owns this context, not the default one
+				int frameLibID = getCastLibIDForContext(frame->sp.ctx);
+				const Cast *ownerCast = (frameLibID == SHARED_CAST_LIB) ? movie->getSharedCast() : movie->getCasts()->getValOrDefault(frameLibID, nullptr);
+				int parentCastId = ownerCast ? ownerCast->getCastIdByScriptId(frame->sp.ctx->_parentNumber) : 0;
+				stackFrame += Common::String::format("(<p%d>): ", parentCastId);
 			}
 
 			if (frame->sp.ctx && frame->sp.ctx->isFactory()) {
@@ -96,20 +102,13 @@ static void renderCallStack(uint pc) {
 		if (ImGui::Selectable(stackFrame.c_str())) {
 			CFrame *head = callstack[callstack.size() - i - 1];
 			ScriptContext *scriptContext = head->sp.ctx;
-			if (!scriptContext || !movie->getCast())
+			if (!scriptContext)
 				continue;
-			int castLibID = movie->getCast()->_castLibID;
-			int castId = head->sp.ctx->_id;
-			bool childScript = false;
-			if (castId == -1) {
-				castId = movie->getCast()->getCastIdByScriptId(head->sp.ctx->_parentNumber);
-				childScript = true;
-			}
+			// the script can live in any cast lib, resolve it from the context
+			int castLibID = getCastLibIDForContext(scriptContext);
+			Common::String moviePath = movie->getArchive()->getPathName().toString();
 
-			ImGuiScript script = toImGuiScript(scriptContext->_scriptType, CastMemberID(castId, castLibID), *head->sp.name);
-			script.byteOffsets = head->sp.ctx->_functionByteOffsets[script.handlerId];
-			script.moviePath = movie->getArchive()->getPathName().toString();
-			script.handlerName = formatHandlerName(head->sp.ctx->_scriptId, castId, script.handlerName, head->sp.ctx->_scriptType, childScript);
+			ImGuiScript script = buildImGuiHandlerScript(scriptContext, castLibID, *head->sp.name, moviePath);
 			script.pc = framePc;
 			setScriptToDisplay(script);
 		}
@@ -133,7 +132,7 @@ static bool renderScriptNavBar(ScriptData &data) {
 	ImGui::BeginDisabled(data._current == 0);
 	if (ImGui::Button(ICON_MS_ARROW_BACK)) {
 		data._current--;
-		_state->_dbg._goToDefinition = true;
+		data._scrollToCurrent = true;
 	}
 	ImGui::EndDisabled();
 	ImGui::SetItemTooltip("Backward");
@@ -142,7 +141,7 @@ static bool renderScriptNavBar(ScriptData &data) {
 	ImGui::BeginDisabled(data._current >= data._scripts.size() - 1);
 	if (ImGui::Button(ICON_MS_ARROW_FORWARD)) {
 		data._current++;
-		_state->_dbg._goToDefinition = true;
+		data._scrollToCurrent = true;
 	}
 	ImGui::EndDisabled();
 	ImGui::SetItemTooltip("Forward");
@@ -155,7 +154,7 @@ static bool renderScriptNavBar(ScriptData &data) {
 			ImGui::PushID(i);
 			if (ImGui::Selectable(data._scripts[i].handlerName.c_str(), &selected)) {
 				data._current = i;
-				_state->_dbg._goToDefinition = true;
+				data._scrollToCurrent = true;
 			}
 			ImGui::PopID();
 		}
@@ -164,10 +163,12 @@ static bool renderScriptNavBar(ScriptData &data) {
 
 	if (!data._scripts[data._current].oldAst) {
 		ImGui::SameLine(0, 20);
-		ImGuiEx::toggleButton(ICON_MS_PACKAGE_2, &data._showByteCode, true);
+		if (selectableViewButton(ICON_MS_PACKAGE_2, !data._showByteCode))
+			data._showByteCode = false;
 		ImGui::SetItemTooltip("Lingo");
 		ImGui::SameLine();
-		ImGuiEx::toggleButton(ICON_MS_STACKS, &data._showByteCode);
+		if (selectableViewButton(ICON_MS_STACKS, data._showByteCode))
+			data._showByteCode = true;
 		ImGui::SetItemTooltip("Bytecode");
 	}
 
@@ -178,7 +179,9 @@ static bool renderScriptNavBar(ScriptData &data) {
 static void renderScriptContext(ScriptData &data, const Movie *movie) {
 	ImGuiScript &current = data._scripts[data._current];
 	ScriptContext *context = getScriptContext(current.id);
-	bool scrollTo = _state->_dbg._goToDefinition;
+	// per-store flag, so two script windows cannot steal each other's scroll
+	bool scrollTo = data._scrollToCurrent;
+	data._scrollToCurrent = false;
 
 	if (!context || context->_functionHandlers.size() == 1) {
 		renderScript(current, data._showByteCode, scrollTo);
@@ -223,7 +226,6 @@ void showScriptsWindow() {
 		}
 	}
 	ImGui::End();
-	_state->_dbg._goToDefinition = false;
 }
 
 static void updateCurrentScript() {
@@ -238,21 +240,15 @@ static void updateCurrentScript() {
 	CFrame *head = callstack[callstack.size() - 1];
 	const Director::Movie *movie = g_director->getCurrentMovie();
 	ScriptContext *scriptContext = head->sp.ctx;
-	if (!scriptContext || !movie->getCast())
+	if (!scriptContext || !movie)
 		return;
-	int castLibID = movie->getCast()->_castLibID;
-	int castId = head->sp.ctx->_id;
-	bool childScript = false;
-	if (castId == -1) {
-		castId = movie->getCast()->getCastIdByScriptId(head->sp.ctx->_parentNumber);
-		childScript = true;
-	}
+	// the script can live in any cast lib, resolve it from the context
+	int castLibID = getCastLibIDForContext(scriptContext);
+	Common::String moviePath = movie->getArchive()->getPathName().toString();
 
-	ImGuiScript script = toImGuiScript(scriptContext->_scriptType, CastMemberID(castId, castLibID), *head->sp.name);
-	script.byteOffsets = scriptContext->_functionByteOffsets[script.handlerId];
-	script.moviePath = movie->getArchive()->getPathName().toString();
-	script.handlerName = formatHandlerName(head->sp.ctx->_scriptId, castId, script.handlerName, head->sp.ctx->_scriptType, childScript);
-	script.pc = 0;
+	ImGuiScript script = buildImGuiHandlerScript(scriptContext, castLibID, *head->sp.name, moviePath);
+	// use the live pc for the current-statement marker
+	script.pc = g_lingo->_state->pc;
 	setScriptToDisplay(script);
 }
 
@@ -263,22 +259,31 @@ void showFuncList() {
 	ImGui::SetNextWindowPos(ImVec2(20, 20), ImGuiCond_FirstUseEver);
 	ImGui::SetNextWindowSize(ImVec2(480, 640), ImGuiCond_FirstUseEver);
 	if (ImGui::Begin("Functions", &_state->_w.funcList)) {
+		Window *selectedWindow = windowListCombo(&_state->_functionsWindow);
+		if (!selectedWindow->getCurrentMovie()) {
+			ImGui::Text("No movie loaded");
+			ImGui::End();
+			return;
+		}
+
 		_state->_functions._nameFilter.Draw();
 		ImGui::Separator();
 
 		// Show a script context wise handlers
-		ImGuiEx::toggleButton(ICON_MS_PACKAGE_2, &_state->_functions._showScriptContexts);
+		if (selectableViewButton(ICON_MS_PACKAGE_2, _state->_functions._showScriptContexts))
+			_state->_functions._showScriptContexts = true;
 		ImGui::SetItemTooltip("Script Contexts");
 
 		ImGui::SameLine();
 		// Show a list of all handlers
-		ImGuiEx::toggleButton(ICON_MS_STACKS, &_state->_functions._showScriptContexts, true);
+		if (selectableViewButton(ICON_MS_STACKS, !_state->_functions._showScriptContexts))
+			_state->_functions._showScriptContexts = false;
 		ImGui::SetItemTooltip("All Handlers");
 
 		const ImVec2 childSize = ImGui::GetContentRegionAvail();
 		ImGui::BeginChild("##functions", ImVec2(childSize.x, childSize.y));
 
-		const Movie *movie = g_director->getCurrentMovie();
+		const Movie *movie = selectedWindow->getCurrentMovie();
 		if (_state->_functions._showScriptContexts) {
 			for (auto cast : *movie->getCasts()) {
 				Common::String castName = Common::String::format("%d", cast._key);
@@ -489,87 +494,37 @@ void showExecutionContext() {
 	ImGui::SetNextWindowPos(ImVec2(20, 160), ImGuiCond_FirstUseEver);
 	ImGui::SetNextWindowSize(ImVec2(500, 750), ImGuiCond_FirstUseEver);
 
-	Director::Lingo *lingo = g_director->getLingo();
-	const Movie *movie = g_director->getCurrentMovie();
-
 	Window *currentWindow = g_director->getCurrentWindow();
 	bool scriptsRendered = false;
 
 	if (ImGui::Begin("Execution Context", &_state->_w.executionContext)) {
-		Window *stage = g_director->getStage();
-		g_director->setCurrentWindow(stage);
-		g_lingo->switchStateFromWindow();
-
-		int windowID = 0;
-		ImGui::PushID(windowID);
-		ImGui::PushStyleColor(ImGuiCol_ChildBg, ImGui::GetStyleColorVec4(ImGuiCol_FrameBg));
-
-		ImGui::BeginChild("Window##", ImGui::GetContentRegionAvail());
-		ImGui::Text("%s", stage->asString().c_str());
-		ImGui::Text("%s", stage->getCurrentMovie()->getMacName().c_str());
-
-		ImGui::SeparatorText("Backtrace");
-		ImVec2 childSize = ImGui::GetContentRegionAvail();
-		childSize.y /= 3;
-		ImGui::BeginChild("##backtrace", childSize);
-		renderCallStack(lingo->_state->pc);
-		ImGui::EndChild();
-
-		ImGui::SeparatorText("Scripts");
-
-		ScriptData *scriptData = &_state->_functions._windowScriptData.getOrCreateVal(stage);
-		updateCurrentScript();
-
-		if (scriptData->_showScript && !scriptData->_scripts.empty()) {
-			bool oldSuppress = _state->_dbg._suppressHighlight;
-			_state->_dbg._suppressHighlight = true;
+		// go-to-definition clicks should open in this window's script list
+		_state->_dbg._hostExecutionContext = true;
 
-			ScriptContext *context = getScriptContext(scriptData->_scripts[scriptData->_current].id);
-			if (context)
-				ImGui::Text("%d:%s type:%s", context->_id, context->getName().c_str(), scriptType2str(context->_scriptType));
-
-			if (renderScriptNavBar(*scriptData)) {
-				ImGui::Separator();
-				childSize = ImGui::GetContentRegionAvail();
-				ImGui::BeginChild("##script", childSize);
-				renderScriptContext(*scriptData, movie);
-				ImGui::EndChild();
-				scriptsRendered = true;
-			}
-
-			_state->_dbg._suppressHighlight = oldSuppress;
-		}
+		Window *selectedWindow = windowListCombo(&_state->_executionContextWindow);
+		Movie *selectedMovie = selectedWindow->getCurrentMovie();
 
-		ImGui::EndChild();
-		ImGui::PopStyleColor();
-		ImGui::PopID();
-
-		ImGui::SameLine();
-
-		const Common::Array<Window *> *windowList = g_director->getWindowList();
-
-		for (auto window : (*windowList)) {
-			g_director->setCurrentWindow(window);
+		if (!selectedMovie) {
+			ImGui::Text("No movie loaded");
+		} else {
+			g_director->setCurrentWindow(selectedWindow);
 			g_lingo->switchStateFromWindow();
 
-			windowID += 1;
-			ImGui::PushID(windowID);
 			ImGui::PushStyleColor(ImGuiCol_ChildBg, ImGui::GetStyleColorVec4(ImGuiCol_FrameBg));
-
-			ImGui::BeginChild("Window##", ImVec2(500.0f, 700.0f), ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY);
-
-			ImGui::Text("%s", window->asString().c_str());
-			ImGui::Text("%s", window->getCurrentMovie()->getMacName().c_str());
+			ImGui::BeginChild("Window##", ImGui::GetContentRegionAvail());
+			ImGui::Text("%s", selectedWindow->asString().c_str());
+			ImGui::Text("%s", selectedMovie->getMacName().c_str());
 
 			ImGui::SeparatorText("Backtrace");
-			childSize = ImGui::GetContentRegionAvail();
+			ImVec2 childSize = ImGui::GetContentRegionAvail();
 			childSize.y /= 3;
 			ImGui::BeginChild("##backtrace", childSize);
-			renderCallStack(lingo->_state->pc);
+			renderCallStack(g_lingo->_state->pc);
 			ImGui::EndChild();
 
 			ImGui::SeparatorText("Scripts");
-			scriptData = &_state->_functions._windowScriptData.getOrCreateVal(window);
+
+			ScriptData *scriptData = &_state->_functions._windowScriptData.getOrCreateVal(selectedWindow);
 			updateCurrentScript();
 
 			if (scriptData->_showScript && !scriptData->_scripts.empty()) {
@@ -584,7 +539,7 @@ void showExecutionContext() {
 					ImGui::Separator();
 					childSize = ImGui::GetContentRegionAvail();
 					ImGui::BeginChild("##script", childSize);
-					renderScriptContext(*scriptData, movie);
+					renderScriptContext(*scriptData, selectedMovie);
 					ImGui::EndChild();
 					scriptsRendered = true;
 				}
@@ -594,15 +549,14 @@ void showExecutionContext() {
 
 			ImGui::EndChild();
 			ImGui::PopStyleColor();
-			ImGui::PopID();
-		}
 
-		// Mark the scripts not dirty after all the handlers have been rendered
-		_state->_dbg._isScriptDirty = !scriptsRendered;
-		_state->_dbg._goToDefinition = false;
+			_state->_dbg._isScriptDirty = !scriptsRendered;
+
+			g_director->setCurrentWindow(currentWindow);
+			g_lingo->switchStateFromWindow();
+		}
 
-		g_director->setCurrentWindow(currentWindow);
-		g_lingo->switchStateFromWindow();
+		_state->_dbg._hostExecutionContext = false;
 	}
 	ImGui::End();
 }
diff --git a/engines/director/debugger/dt-search.cpp b/engines/director/debugger/dt-search.cpp
index 454f5400449..e69a8ea7b34 100644
--- a/engines/director/debugger/dt-search.cpp
+++ b/engines/director/debugger/dt-search.cpp
@@ -221,7 +221,6 @@ void showSearchBar() {
 				q.toLowercase();
 				_state->_dbg._highlightQuery = q;
 				addToOpenHandlers(script);
-				_state->_dbg._goToDefinition = true;
 			}
 
 			ImGui::TableNextColumn();


Commit: 28609589d534b97e307e5ebda7f99cfce73facd0
    https://github.com/scummvm/scummvm/commit/28609589d534b97e307e5ebda7f99cfce73facd0
Author: ramyak-sharma (ramyaksharma1 at gmail.com)
Date: 2026-07-11T01:17:05+02:00

Commit Message:
DIRECTOR: DT: Show the sprite script in the sprite inspector

Clicking a sprite in the Score opens the Cast Details of the sprite's
cast member, so the script it surfaces is the cast member's script.
There was no way to reach the sprite (behavior) script from the Score
window at all: Behavior mode only displays its name, and the
clickable reference in the Channels window is limited to the current
frame.

The sprite inspector now shows a Script line under the cast member ID
and sprite type, rendering the sprite's _scriptId through
displayScriptRef(), the same clickable reference used by the Channels
window. Clicking it opens the script's handlers in the Scripts
window; sprites without a behavior show "none".

Changed paths:
    engines/director/debugger/dt-score.cpp


diff --git a/engines/director/debugger/dt-score.cpp b/engines/director/debugger/dt-score.cpp
index 232a034f4ae..c874464164d 100644
--- a/engines/director/debugger/dt-score.cpp
+++ b/engines/director/debugger/dt-score.cpp
@@ -448,8 +448,15 @@ static void drawSpriteInspector(Score *score, Cast *cast, uint numFrames) {
 				ImGui::InvisibleButton("##canvas", ImVec2(32.f, 32.f));
 			}
 			ImGui::SameLine();
+			ImGui::BeginGroup();
 			ImGui::Text("%s", sprite->_castId.asString().c_str());
 			ImGui::Text("%s", spriteType2str(sprite->_spriteType));
+			ImGui::Text("Script:"); ImGui::SameLine();
+			if (sprite->_scriptId.member)
+				displayScriptRef(sprite->_scriptId);
+			else
+				ImGui::TextDisabled("none");
+			ImGui::EndGroup();
 		}
 
 		ImGui::PopStyleColor();


Commit: 10405a1f525301f05f9dc6aca53ab5f3fe24b68a
    https://github.com/scummvm/scummvm/commit/10405a1f525301f05f9dc6aca53ab5f3fe24b68a
Author: ramyak-sharma (ramyaksharma1 at gmail.com)
Date: 2026-07-11T01:17:05+02:00

Commit Message:
DIRECTOR: DT: Fix movie listing and goto behavior in Windows viewer

The "All Movies" list only matched *.dir files, so it came up empty
for D2/D3 games (.mmm), protected movies (.dxr) and Shockwave movies
(.dcr). All four extensions are now listed.

Jumping to a movie from the list called func_goto() without the
commandgo flag, unlike every other jump in the debugger (control
panel, score double-click). Without it, resetLingoGo() is skipped and
custom event handlers registered by the previous movie survive the
swap.

The state column now also labels the kPlayLoaded and
kPlayPausedAfterLoading states instead of showing "other", and
windows without a title show "(untitled)" instead of an empty cell.

Changed paths:
    engines/director/debugger/dt-lists.cpp


diff --git a/engines/director/debugger/dt-lists.cpp b/engines/director/debugger/dt-lists.cpp
index 3bf2ac026f5..bd5b25c06d1 100644
--- a/engines/director/debugger/dt-lists.cpp
+++ b/engines/director/debugger/dt-lists.cpp
@@ -408,7 +408,10 @@ void showWindows() {
 			bool isStage = (window == g_director->getStage());
 			const char *title = isStage ? "(stage)" :
 				(window->getMacWindow() ? window->getMacWindow()->getTitle().c_str() : "");
-			ImGui::TextUnformatted(title);
+			if (*title)
+				ImGui::TextUnformatted(title);
+			else
+				ImGui::TextDisabled("(untitled)");
 
 			Movie *movie = window->getCurrentMovie();
 
@@ -433,11 +436,12 @@ void showWindows() {
 				if (score) {
 					const char *stateStr = "unknown";
 					switch (score->_playState) {
-					case kPlayNotStarted: stateStr = "not started"; break;
-					case kPlayStarted:    stateStr = "playing";     break;
-					case kPlayStopped:    stateStr = "stopped";     break;
-					case kPlayPaused:     stateStr = "paused";      break;
-					default:              stateStr = "other";       break;
+					case kPlayNotStarted:         stateStr = "not started"; break;
+					case kPlayLoaded:             stateStr = "loaded";      break;
+					case kPlayStarted:            stateStr = "playing";     break;
+					case kPlayStopped:            stateStr = "stopped";     break;
+					case kPlayPaused:             stateStr = "paused";      break;
+					case kPlayPausedAfterLoading: stateStr = "paused (after load)"; break;
 					}
 					ImGui::TextUnformatted(stateStr);
 				}
@@ -471,9 +475,14 @@ void showWindows() {
 
 	ImGui::SeparatorText("All Movies");
 
+	// .dir (D4+), .dxr (protected), .dcr (Shockwave), .mmm (D2/D3)
+	static const char *moviePatterns[] = {
+		"*.dir", "*.DIR", "*.dxr", "*.DXR",
+		"*.dcr", "*.DCR", "*.mmm", "*.MMM",
+	};
 	Common::ArchiveMemberList dirFiles;
-	SearchMan.listMatchingMembers(dirFiles, "*.dir");
-	SearchMan.listMatchingMembers(dirFiles, "*.DIR");
+	for (auto pattern : moviePatterns)
+		SearchMan.listMatchingMembers(dirFiles, pattern);
 
 	// deduplicate by name
 	Common::HashMap<Common::String, bool, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> seen;
@@ -502,7 +511,8 @@ void showWindows() {
 				Datum frame, movie;
 				movie.type = STRING;
 				movie.u.s = new Common::String(f->getName());
-				g_lingo->func_goto(frame, movie);
+				// commandgo resets go-registered handlers on the movie swap
+				g_lingo->func_goto(frame, movie, true);
 			}
 		}
 


Commit: fb257ea0e4aaa6693c219117d7d1b06bb00bd064
    https://github.com/scummvm/scummvm/commit/fb257ea0e4aaa6693c219117d7d1b06bb00bd064
Author: ramyak-sharma (ramyaksharma1 at gmail.com)
Date: 2026-07-11T01:17:05+02:00

Commit Message:
DIRECTOR: DT: Resolve sprite members through the movie in the Score

All cast member lookups in the Score window went through the movie's
default cast lib only. A sprite whose member lives in the shared cast
(common in D4 games) or another cast lib resolved to null: clicking it
left the sprite inspector completely empty and did not open Cast
Details, and Member/Behavior mode labels stayed blank. With a
same-numbered member in the default lib it silently resolved to the
wrong member.

All nine lookup sites now use Movie::getCastMember() with the full
CastMemberID, which honors the cast lib and falls back to the shared
cast.

Changed paths:
    engines/director/debugger/dt-score.cpp


diff --git a/engines/director/debugger/dt-score.cpp b/engines/director/debugger/dt-score.cpp
index c874464164d..fa57a20c3d7 100644
--- a/engines/director/debugger/dt-score.cpp
+++ b/engines/director/debugger/dt-score.cpp
@@ -395,7 +395,7 @@ static void drawModeSelector(ImVec2 startPos) {
 	}
 }
 
-static void drawSpriteInspector(Score *score, Cast *cast, uint numFrames) {
+static void drawSpriteInspector(Score *score, Movie *movie, uint numFrames) {
 
 	if (_state->_scoreFrameOffset >= (int)numFrames)
 		_state->_scoreFrameOffset = 1;
@@ -411,7 +411,9 @@ static void drawSpriteInspector(Score *score, Cast *cast, uint numFrames) {
 			sprite = score->_scoreCache[_state->_selectedScoreCast.frame]->_sprites[_state->_selectedScoreCast.channel];
 
 		if (sprite) {
-			castMember = cast->getCastMember(sprite->_castId.member, true);
+			// resolve through the movie: the member can live in any cast lib
+			// or the shared cast, not just the default one
+			castMember = movie->getCastMember(sprite->_castId);
 
 			shape = sprite->isQDShape();
 		}
@@ -594,8 +596,9 @@ static void drawSpriteInspector(Score *score, Cast *cast, uint numFrames) {
 
 }
 
-static void drawSpriteGrid(ImDrawList *dl, ImVec2 startPos, Score *score, Cast *cast, Window *window) {
+static void drawSpriteGrid(ImDrawList *dl, ImVec2 startPos, Score *score, Window *window) {
 	int total_frames = (int)score->_scoreCache.size();
+	Movie *movie = window->getCurrentMovie();
 
 	auto &cfg = _state->_scoreCfg;
 	float cellH = (_state->_scoreMode == kModeExtended) ? cfg._cellHeightExtended : cfg._cellHeight;
@@ -675,7 +678,7 @@ static void drawSpriteGrid(ImDrawList *dl, ImVec2 startPos, Score *score, Cast *
 				dl->AddRect(ImVec2(x2 - 7.0f, cy - 3.0f), ImVec2(x2 - 1.0f, cy + 3.0f), U32(_state->theme->line_color), 0.0f, 0, 1.5f);
 
 			float spanWidth = x2 - x1 - 8.0f; // available width inside the span bar
-			CastMember *cm = cast->getCastMember(sprite._castId.member, true);
+			CastMember *cm = movie->getCastMember(sprite._castId);
 			if (spanWidth < 4.0f) continue;
 
 			if (_state->_scoreMode == kModeExtended && startVisible && (sprite._castId.member || sprite.isQDShape())) {
@@ -686,7 +689,7 @@ static void drawSpriteGrid(ImDrawList *dl, ImVec2 startPos, Score *score, Cast *
 				Common::String member = cm ? getDisplayName(cm) : (sprite.isQDShape() ? "Q" : "");
 				Common::String behavior;
 				if (sprite._scriptId.member) {
-					CastMember *sc = cast->getCastMember(sprite._scriptId.member, true);
+					CastMember *sc = movie->getCastMember(sprite._scriptId);
 					if (sc) behavior = getDisplayName(sc);
 				}
 				Common::String ink = inkType2str(sprite._ink);
@@ -712,7 +715,7 @@ static void drawSpriteGrid(ImDrawList *dl, ImVec2 startPos, Score *score, Cast *
 					break;
 				case kModeBehavior:
 					if (sprite._scriptId.member) {
-						CastMember *script = cast->getCastMember(sprite._scriptId.member, true);
+						CastMember *script = movie->getCastMember(sprite._scriptId);
 						if (script) label = getDisplayName(script);
 					}
 					break;
@@ -763,7 +766,7 @@ static void drawSpriteGrid(ImDrawList *dl, ImVec2 startPos, Score *score, Cast *
 
 					// Open cast member details window
 					if (sprite._castId.member) {
-						CastMember *clickedCM = cast->getCastMember(sprite._castId.member, true);
+						CastMember *clickedCM = movie->getCastMember(sprite._castId);
 						if (clickedCM) {
 							_state->_castDetails._castMemberID = CastMemberID(clickedCM->getID(), clickedCM->getCast()->_castLibID);
 							_state->_w.castDetails = true;
@@ -795,14 +798,14 @@ static void drawSpriteGrid(ImDrawList *dl, ImVec2 startPos, Score *score, Cast *
 					_state->_hoveredScoreCast.channel = ch;
 
 					if (sprite._castId.member || sprite.isQDShape()) {
-						CastMember *cm = cast->getCastMember(sprite._castId.member, true);
+						CastMember *cm = movie->getCastMember(sprite._castId);
 
 						if (_state->_scoreMode == kModeExtended) {
 							// build multi-line tooltip for extended mode
 							Common::String member = cm ? getDisplayName(cm) : (sprite.isQDShape() ? "Q" : "");
 							Common::String behavior;
 							if (sprite._scriptId.member) {
-								CastMember *sc = cast->getCastMember(sprite._scriptId.member, true);
+								CastMember *sc = movie->getCastMember(sprite._scriptId);
 								if (sc) behavior = getDisplayName(sc);
 							}
 							Common::String ink = inkType2str(sprite._ink);
@@ -827,7 +830,7 @@ static void drawSpriteGrid(ImDrawList *dl, ImVec2 startPos, Score *score, Cast *
 								break;
 							case kModeBehavior:
 								if (sprite._scriptId.member) {
-									CastMember *sc = cast->getCastMember(sprite._scriptId.member, true);
+									CastMember *sc = movie->getCastMember(sprite._scriptId);
 									if (sc) label = getDisplayName(sc);
 								}
 								break;
@@ -1171,9 +1174,9 @@ void showScore() {
 		}
 
 		buildContinuationData(selectedWindow);
-		Score *score = selectedWindow->getCurrentMovie()->getScore();
+		Movie *movie = selectedWindow->getCurrentMovie();
+		Score *score = movie->getScore();
 		uint numFrames = score->_scoreCache.size();
-		Cast *cast = selectedWindow->getCurrentMovie()->getCast();
 
 		if (!numFrames) {
 			ImGui::Text("No frames");
@@ -1187,7 +1190,7 @@ void showScore() {
 		if (!numFrames || _state->_selectedScoreCast.channel >= (int)score->_scoreCache[0]->_sprites.size())
 			_state->_selectedScoreCast.channel = 0;
 
-		drawSpriteInspector(score, cast, numFrames);
+		drawSpriteInspector(score, movie, numFrames);
 		int numChannels = MIN<int>(score->_scoreCache[0]->_sprites.size(), score->_maxChannelsUsed + 10);
 		handleScrolling(score, numChannels);
 
@@ -1201,7 +1204,7 @@ void showScore() {
 		drawModeSelector(layout.modeSelectorPos);
 		drawRuler(dl, layout.rulerPos);
 		drawSidebar2(dl, layout.sidebar2Pos, score, selectedWindow);
-		drawSpriteGrid(dl, layout.gridPos, score, cast, selectedWindow);
+		drawSpriteGrid(dl, layout.gridPos, score, selectedWindow);
 		drawPlayhead(dl, layout.rulerPos, layout.mainChannelGridPos, layout.gridPos, score);
 		drawSliderX(layout.sliderPos, score);
 		drawSliderY(layout.sliderYPos, numChannels);




More information about the Scummvm-git-logs mailing list