[Scummvm-git-logs] scummvm master -> 1ce0e58963b66f7cd2d329727dfa2c18e7f53bc3

dreammaster noreply at scummvm.org
Sat Aug 8 02:20:26 UTC 2026


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

Summary:
71cc09a8ed M4: RIDDLE: Fix hang in the conversation with Wolf the gardener
515f317296 M4: RIDDLE: Fix room 402 freezing when the conversation with Wolf ends
1ce0e58963 M4: RIDDLE: Clock room 402's talk chain off Rip's own machine


Commit: 71cc09a8ed9eede969f24cd1a542bfda5cafb698
    https://github.com/scummvm/scummvm/commit/71cc09a8ed9eede969f24cd1a542bfda5cafb698
Author: sloanext (46776957+sloanext at users.noreply.github.com)
Date: 2026-08-08T12:20:21+10:00

Commit Message:
M4: RIDDLE: Fix hang in the conversation with Wolf the gardener

The conversation in room 402 is driven by two self-sustaining daemon
chains (Ripley: 102-103-102, Wolf: 110-111-110). Several transitions
assigned states that no dispatcher handles, so the chain fell through to
default and died silently. The next node that needs it then froze the
game with the commands disabled and the walker hidden.

(The issue exists in all versions of the game; only known fix on DOSBox
is lowering cycles.)

Concrete defects:
- node 21 set _wolfShould to 201, which is a daemon trigger number, not
  a Wolf state. Node 26 uses 2101 for the same construct.
- nodes 7/12/25 and daemon state 1112 set 2142 unconditionally, but 2142
  only exists in mode 2002. The default branch and conv402a777() already
  pick the state by mode.
- state 2265 restored the trigger mode to KT_PARSE instead of KT_DAEMON.

Beyond that, states that park the chain on purpose (2105, 2153, 2233,
2234, 2262, 2265, 2272) handed control back to the conversation without
ever restarting it, which is what made the hang depend on whether a
110/111 message happened to still be in flight - the same timing
sensitivity the original shows under DOSBox. Track whether the chain is
parked and restart it from the conversation callbacks when a new Wolf
state is requested, guarded so two chains can never run at once.

Finally, give the speech-driven states a fallback when their sound name
is empty (an empty name is a fatal error() in Digi::preload), add the
missing conv_resume() in 2265, and warn instead of silently falling
through on an unhandled state.

Assisted-by: Claude:claude-opus-5

Changed paths:
    engines/m4/riddle/rooms/section4/room402.cpp
    engines/m4/riddle/rooms/section4/room402.h


diff --git a/engines/m4/riddle/rooms/section4/room402.cpp b/engines/m4/riddle/rooms/section4/room402.cpp
index 737f57b7bfc..f66d7820b10 100644
--- a/engines/m4/riddle/rooms/section4/room402.cpp
+++ b/engines/m4/riddle/rooms/section4/room402.cpp
@@ -55,6 +55,7 @@ void Room402::init() {
 		_dialogShould = 0;
 		_wolfMode = 0;
 		_wolfShould = 0;
+		_wolfChainIdle = false;
 		_sound1.clear();
 		_sound2.clear();
 	}
@@ -521,6 +522,13 @@ void Room402::daemon() {
 					digi_play(_sound1.c_str(), 1, 255, 777);
 					_G(kernel).trigger_mode = KT_DAEMON;
 					_sound1.clear();
+				} else {
+					// Without the speech nothing would drive the Rip chain
+					// on. Behave as if the line had just finished playing,
+					// like the 777 handler in conv402a777() does.
+					_dialogShould = 1103;
+					kernel_timing_trigger(1, 102);
+					conv_resume();
 				}
 				break;
 
@@ -546,6 +554,8 @@ void Room402::daemon() {
 				break;
 
 			default:
+				warning("Room402: unhandled _dialogShould %d in mode %d",
+					_dialogShould, _dialogMode);
 				break;
 			}
 			break;
@@ -612,12 +622,15 @@ void Room402::daemon() {
 					player_set_commands_allowed(true);
 				} else {
 					_val6 = 1;
+					_wolfShould = (_wolfMode == 2002) ? 2142 : 2101;
+					_dialogShould = 1113;
 
 					if (!_sound2.empty()) {
-						_wolfShould = 2142;
-						_dialogShould = 1113;
 						digi_play(_sound2.c_str(), 1, 255, 103);
 						_sound2.clear();
+					} else {
+						// Without the speech nothing would trigger 103
+						kernel_timing_trigger(1, 103);
 					}
 				}
 				break;
@@ -638,16 +651,22 @@ void Room402::daemon() {
 				break;
 
 			default:
+				warning("Room402: unhandled _dialogShould %d in mode %d",
+					_dialogShould, _dialogMode);
 				break;
 			}
 			break;
 
 		default:
+			warning("Room402: unhandled _dialogMode %d", _dialogMode);
 			break;
 		}
 		break;
 
 	case 110:
+		// The chain is running again, so it doesn't need to be kicked
+		_wolfChainIdle = false;
+
 		switch (_wolfMode) {
 		case 2000:
 			if (_trigger1 == -1) {
@@ -666,6 +685,8 @@ void Room402::daemon() {
 					kernel_timing_trigger(1, _trigger1);
 					_trigger1 = -1;
 				}
+			} else {
+				_wolfChainIdle = true;
 			}
 			break;
 
@@ -679,6 +700,7 @@ void Room402::daemon() {
 			break;
 
 		default:
+			_wolfChainIdle = true;
 			break;
 		}
 		break;
@@ -707,6 +729,7 @@ void Room402::daemon() {
 			case 2105:
 				sendWSMessage_10000(1, _wolfieMach, _wolfClipping, 8, 10, -1,
 					_wolfClipping, 10, 10, 0);
+				_wolfChainIdle = true;
 				break;
 
 			case 2106:
@@ -774,11 +797,13 @@ void Room402::daemon() {
 					series_stream("Rip popup", 7, 0, 111);
 				} else {
 					player_set_commands_allowed(true);
+					_wolfChainIdle = true;
 				}
 				break;
 
 			case 2234:
 				player_set_commands_allowed(true);
+				_wolfChainIdle = true;
 				break;
 
 			case 2240:
@@ -827,6 +852,9 @@ void Room402::daemon() {
 				break;
 
 			default:
+				warning("Room402: unhandled _wolfShould %d in mode %d",
+					_wolfShould, _wolfMode);
+				_wolfChainIdle = true;
 				break;
 			}
 			break;
@@ -836,6 +864,8 @@ void Room402::daemon() {
 				sendWSMessage_10000(1, _wolfieMach, _wolfClipping, 1, 10, 110,
 					_wolfClipping, 10, 10, 0);
 				kernel_timing_trigger(35, 10000);
+			} else {
+				_wolfChainIdle = true;
 			}
 			break;
 
@@ -881,6 +911,7 @@ void Room402::daemon() {
 
 			case 2153:
 				player_set_commands_allowed(true);
+				_wolfChainIdle = true;
 				break;
 
 			case 2160:
@@ -904,12 +935,20 @@ void Room402::daemon() {
 				if (!_sound2.empty()) {
 					digi_play(_sound2.c_str(), 1, 255, 111);
 					_sound2.clear();
+				} else {
+					// Without the speech there is nothing left to trigger 111
+					kernel_timing_trigger(1, 111);
 				}
 				break;
 
 			case 2180:
 				_wolfShould = 2181;
-				digi_play(_sound2.c_str(), 1, 255, 111);
+
+				if (!_sound2.empty()) {
+					digi_play(_sound2.c_str(), 1, 255, 111);
+				} else {
+					kernel_timing_trigger(1, 111);
+				}
 				break;
 
 			case 2181:
@@ -967,6 +1006,7 @@ void Room402::daemon() {
 						_currentNode == 14 || _currentNode == 26) {
 					sendWSMessage_10000(1, _wolfieMach, _wolfWantsMoney, 1, 16, -1,
 						_wolfWantsMoney, 16, 16, 0);
+					_wolfChainIdle = true;
 
 					if (!_sound2.empty()) {
 						_G(kernel).trigger_mode = KT_PARSE;
@@ -1001,27 +1041,38 @@ void Room402::daemon() {
 				break;
 
 			case 2265:
+				_wolfChainIdle = true;
+
 				if (!_sound2.empty()) {
 					_sound2.clear();
 					_G(kernel).trigger_mode = KT_PARSE;
 					kernel_timing_trigger(1, 777);
-					_G(kernel).trigger_mode = KT_PARSE;
+					_G(kernel).trigger_mode = KT_DAEMON;
 
 				} else {
 					player_set_commands_allowed(true);
+					// Nothing else hands control back to the conversation here.
+					// conv_resume() is a no-op when none is active.
+					conv_resume();
 				}
 				break;
 
 			case 2272:
+				_wolfChainIdle = true;
 				conv_resume();
 				break;
 
 			default:
+				warning("Room402: unhandled _wolfShould %d in mode %d",
+					_wolfShould, _wolfMode);
+				_wolfChainIdle = true;
 				break;
 			}
 			break;
 
 		default:
+			warning("Room402: unhandled _wolfMode %d", _wolfMode);
+			_wolfChainIdle = true;
 			break;
 		}
 		break;
@@ -1274,7 +1325,11 @@ void Room402::daemon() {
 			484, 315, 11, triggerMachineByHashCallback3000, "wolf_walker");
 		sendWSMessage_10000(_wolfWalker, 517, 239, 9, -1, 0);
 
-		digi_play(_sound2.c_str(), 1);
+		if (!_sound2.empty()) {
+			digi_play(_sound2.c_str(), 1);
+			_sound2.clear();
+		}
+
 		kernel_timing_trigger(80, 303);
 		break;
 
@@ -1564,6 +1619,24 @@ void Room402::parser() {
 	_G(player).command_ready = false;
 }
 
+void Room402::kickWolfChain() {
+	// Several states park the Wolf animation chain (they finish with trigger
+	// -1 and hand control back to the conversation). The conversation
+	// callbacks below then assign a new _wolfShould that nothing would ever
+	// dispatch, leaving the game frozen with the commands disabled. Restart
+	// the chain in that case - but only when it really is parked, since two
+	// concurrent chains would multiply with every round.
+	if (!_wolfChainIdle)
+		return;
+
+	_wolfChainIdle = false;
+
+	const KernelTriggerType oldMode = _G(kernel).trigger_mode;
+	_G(kernel).trigger_mode = KT_DAEMON;
+	kernel_timing_trigger(1, 110);
+	_G(kernel).trigger_mode = oldMode;
+}
+
 void Room402::conv402a() {
 	const char *sound = conv_sound_to_play();
 	const int who = conv_whos_talking();
@@ -1574,6 +1647,8 @@ void Room402::conv402a() {
 	if (!sound) {
 		conv_resume();
 	} else if (who <= 0) {
+		const int prevWolfShould = _wolfShould;
+
 		switch (node) {
 		case 4:
 		case 9:
@@ -1602,7 +1677,7 @@ void Room402::conv402a() {
 				_wolfMode = 2002;
 				_wolfShould = 2160;
 			} else {
-				_wolfShould = 2142;
+				_wolfShould = (_wolfMode == 2002) ? 2142 : 2101;
 				digi_play(sound, 1, 255, 777);
 			}
 			break;
@@ -1661,7 +1736,7 @@ void Room402::conv402a() {
 				_wolfMode = 2002;
 				_wolfShould = 2260;
 			} else {
-				_wolfShould = 201;
+				_wolfShould = (_wolfMode == 2002) ? 2142 : 2101;
 				digi_play(sound, 1, 255, 777);
 			}
 			break;
@@ -1727,6 +1802,10 @@ void Room402::conv402a() {
 			digi_play(sound, 1, 255, 777);
 			break;
 		}
+
+		// A new Wolf state was requested - make sure something dispatches it
+		if (_wolfShould != prevWolfShould)
+			kickWolfChain();
 	} else if (who == 1) {
 		switch (node) {
 		case 1:
@@ -1808,6 +1887,7 @@ void Room402::conv402a777() {
 			_G(kernel).trigger_mode = KT_PARSE;
 		} else {
 			_wolfShould = (_wolfMode == 2002) ? 2143 : 2104;
+			kickWolfChain();
 			conv_resume();
 		}
 	} else if (who == 1) {
diff --git a/engines/m4/riddle/rooms/section4/room402.h b/engines/m4/riddle/rooms/section4/room402.h
index 2a531a41036..dfa57cffa53 100644
--- a/engines/m4/riddle/rooms/section4/room402.h
+++ b/engines/m4/riddle/rooms/section4/room402.h
@@ -41,6 +41,9 @@ private:
 	int _dialogShould = 0;
 	int _wolfMode = 0;
 	int _wolfShould = 0;
+	// Set when the 110/111 animation chain has been parked, so that a Wolf
+	// state assigned from the conversation callbacks can restart it.
+	bool _wolfChainIdle = false;
 	int _val14 = 0;
 	machine *_wolfie = nullptr;
 	machine *_wolfieMach = nullptr;
@@ -73,6 +76,7 @@ private:
 	Common::String _sound1;
 	Common::String _sound2;
 
+	void kickWolfChain();
 	void conv402a();
 	void conv402a777();
 	void useTopiary();


Commit: 515f31729630e0b9fa33410bbddc655b8343063b
    https://github.com/scummvm/scummvm/commit/515f31729630e0b9fa33410bbddc655b8343063b
Author: sloanext (46776957+sloanext at users.noreply.github.com)
Date: 2026-08-08T12:20:21+10:00

Commit Message:
M4: RIDDLE: Fix room 402 freezing when the conversation with Wolf ends

conv_unload() restores the commands to the value conv_load() saved, which
is false here because room 402 disables them before loading the
conversation, and then fires the finish trigger, daemon 101. That case only
assigns _dialogShould and never dispatches it - but 1105 and 1112 are the
states that terminate Ripley's talker machine, unhide the walker and re-enable
the commands. They ran only when a 102/103 message happened to still be in
flight.

State 1122 ("Rip shows Wolf the letter", the first dialogue option) parks
the chain: it plays its line with the 777 trigger and sends no animation
message, and conv402a777() then only assigns _dialogShould and resumes. So
the conversation ends with the chain dead, the interface comes back and the
game sits there with the hourglass cursor.

Track whether Ripley's chain is parked, the same way the Wolf chain already
does, and restart it from trigger 101 - so the teardown state always runs
exactly once - as well as from the conversation callbacks.

Two fixes to the Wolf chain watchdog while here:
- _wolfChainIdle was only cleared in case 110, but many sub-chains loop
  through 111 alone and never reach 110, so a stale value could survive and
  start a second concurrent chain. Both chains message the same machine, the
  second overrides the first, and its completion trigger is lost - which
  stopped the clipping animation.
- Restore the guard in state 1112 that I had removed: _dialogShould = 1113
  assigns a mode 2002 Wolf state without touching _wolfMode, so it must stay
  inside the branch that has a line to play.

Assisted-by: Claude:claude-opus-5

Changed paths:
    engines/m4/riddle/rooms/section4/room402.cpp
    engines/m4/riddle/rooms/section4/room402.h


diff --git a/engines/m4/riddle/rooms/section4/room402.cpp b/engines/m4/riddle/rooms/section4/room402.cpp
index f66d7820b10..b9fe36e5301 100644
--- a/engines/m4/riddle/rooms/section4/room402.cpp
+++ b/engines/m4/riddle/rooms/section4/room402.cpp
@@ -53,6 +53,7 @@ void Room402::init() {
 		_currentNode = -1;
 		_dialogMode = 0;
 		_dialogShould = 0;
+		_dialogChainIdle = false;
 		_wolfMode = 0;
 		_wolfShould = 0;
 		_wolfChainIdle = false;
@@ -396,23 +397,35 @@ void Room402::daemon() {
 		break;
 
 	case 101:
+		// The conversation has finished. conv_unload() has already restored
+		// the commands to the value saved by conv_load(), which is false here,
+		// so the teardown states 1105 / 1112 are the only thing that gives
+		// control back. They have to be dispatched, not just requested.
 		switch (_dialogMode) {
 		case 1000:
 			_dialogShould = 1105;
+			kickDialogChain();
 			break;
 		case 1001:
-			if (_G(flags)[V132])
+			if (_G(flags)[V132]) {
 				_trigger1 = 300;
-			else
+				// _trigger1 is consumed by the Wolf chain
+				kickWolfChain();
+			} else {
 				_dialogShould = 1112;
+				kickDialogChain();
+			}
 			break;
 
 		default:
+			warning("Room402: conversation finished in _dialogMode %d", _dialogMode);
 			break;
 		}
 		break;
 
 	case 102:
+		_dialogChainIdle = false;
+
 		if (_val2 == -1) {
 			kernel_timing_trigger(1, 103);
 		} else {
@@ -422,6 +435,9 @@ void Room402::daemon() {
 		break;
 
 	case 103:
+		// The chain is running again, so it doesn't need to be kicked
+		_dialogChainIdle = false;
+
 		switch (_dialogMode) {
 		case 1000:
 			switch (_dialogShould) {
@@ -478,6 +494,7 @@ void Room402::daemon() {
 			case 1104:
 				sendWSMessage_10000(1, _ripEnterLeave, _ripTalker, 1, 1, -1,
 					_ripTalker, 1, 1, 0);
+				_dialogChainIdle = true;
 				break;
 
 			case 1105:
@@ -504,6 +521,9 @@ void Room402::daemon() {
 				}
 
 				player_set_commands_allowed(true);
+				_dialogChainIdle = true;
+				// The Wolf state assigned above still needs dispatching
+				kickWolfChain();
 				break;
 
 			case 1120:
@@ -517,17 +537,19 @@ void Room402::daemon() {
 				break;
 
 			case 1122:
+				// Nothing here re-arms the chain - the 777 handler picks it up
+				_dialogChainIdle = true;
+
 				if (!_sound1.empty()) {
 					_G(kernel).trigger_mode = KT_PARSE;
 					digi_play(_sound1.c_str(), 1, 255, 777);
 					_G(kernel).trigger_mode = KT_DAEMON;
 					_sound1.clear();
 				} else {
-					// Without the speech nothing would drive the Rip chain
-					// on. Behave as if the line had just finished playing,
-					// like the 777 handler in conv402a777() does.
+					// Without the speech there is no 777 either, so behave as
+					// if the line had just finished playing
 					_dialogShould = 1103;
-					kernel_timing_trigger(1, 102);
+					kickDialogChain();
 					conv_resume();
 				}
 				break;
@@ -556,6 +578,7 @@ void Room402::daemon() {
 			default:
 				warning("Room402: unhandled _dialogShould %d in mode %d",
 					_dialogShould, _dialogMode);
+				_dialogChainIdle = true;
 				break;
 			}
 			break;
@@ -612,6 +635,8 @@ void Room402::daemon() {
 				_G(flags)[V114] = 0;
 				_G(flags)[V112] = 0;
 
+				_dialogChainIdle = true;
+
 				if (_currentNode == 19 || _currentNode == 22 || _currentNode == 23) {
 					_wolfMode = 2002;
 					_wolfShould = 2190;
@@ -620,23 +645,23 @@ void Room402::daemon() {
 					_wolfMode = 2001;
 					_wolfShould = 2300;
 					player_set_commands_allowed(true);
+					kickWolfChain();
 				} else {
 					_val6 = 1;
-					_wolfShould = (_wolfMode == 2002) ? 2142 : 2101;
-					_dialogShould = 1113;
 
 					if (!_sound2.empty()) {
+						_wolfShould = (_wolfMode == 2002) ? 2142 : 2101;
+						_dialogShould = 1113;
 						digi_play(_sound2.c_str(), 1, 255, 103);
 						_sound2.clear();
-					} else {
-						// Without the speech nothing would trigger 103
-						kernel_timing_trigger(1, 103);
 					}
 				}
 				break;
 
 			case 1113:
 				_wolfShould = 2150;
+				_dialogChainIdle = true;
+				kickWolfChain();
 				break;
 
 			case 1114:
@@ -653,12 +678,14 @@ void Room402::daemon() {
 			default:
 				warning("Room402: unhandled _dialogShould %d in mode %d",
 					_dialogShould, _dialogMode);
+				_dialogChainIdle = true;
 				break;
 			}
 			break;
 
 		default:
 			warning("Room402: unhandled _dialogMode %d", _dialogMode);
+			_dialogChainIdle = true;
 			break;
 		}
 		break;
@@ -706,6 +733,11 @@ void Room402::daemon() {
 		break;
 
 	case 111:
+		// The chain is running again, so it doesn't need to be kicked. Many
+		// sub-chains loop through 111 only and never pass through 110, so
+		// clearing it there alone would leave a stale value behind.
+		_wolfChainIdle = false;
+
 		switch (_wolfMode) {
 		case 2000:
 			switch (_wolfShould) {
@@ -1637,6 +1669,23 @@ void Room402::kickWolfChain() {
 	_G(kernel).trigger_mode = oldMode;
 }
 
+void Room402::kickDialogChain() {
+	// Same problem on Rip's side: state 1122 plays its line and waits for the
+	// 777 trigger without sending an animation message, so the 102/103 chain
+	// is parked. Trigger 101 then requests the teardown state 1105 / 1112 -
+	// the only thing that unhides the walker and re-enables the commands -
+	// and nothing would ever dispatch it.
+	if (!_dialogChainIdle)
+		return;
+
+	_dialogChainIdle = false;
+
+	const KernelTriggerType oldMode = _G(kernel).trigger_mode;
+	_G(kernel).trigger_mode = KT_DAEMON;
+	kernel_timing_trigger(1, 102);
+	_G(kernel).trigger_mode = oldMode;
+}
+
 void Room402::conv402a() {
 	const char *sound = conv_sound_to_play();
 	const int who = conv_whos_talking();
@@ -1807,6 +1856,8 @@ void Room402::conv402a() {
 		if (_wolfShould != prevWolfShould)
 			kickWolfChain();
 	} else if (who == 1) {
+		const int prevDialogShould = _dialogShould;
+
 		switch (node) {
 		case 1:
 			if (entry == 3) {
@@ -1870,6 +1921,10 @@ void Room402::conv402a() {
 			digi_play(sound, 1, 255, 777);
 			break;
 		}
+
+		// A new Rip state was requested - make sure something dispatches it
+		if (_dialogShould != prevDialogShould)
+			kickDialogChain();
 	}
 }
 
@@ -1892,6 +1947,7 @@ void Room402::conv402a777() {
 		}
 	} else if (who == 1) {
 		_dialogShould = (_dialogMode == 1001) ? 1115 : 1103;
+		kickDialogChain();
 		conv_resume();
 	}
 }
diff --git a/engines/m4/riddle/rooms/section4/room402.h b/engines/m4/riddle/rooms/section4/room402.h
index dfa57cffa53..05dc6e25e38 100644
--- a/engines/m4/riddle/rooms/section4/room402.h
+++ b/engines/m4/riddle/rooms/section4/room402.h
@@ -39,6 +39,9 @@ private:
 	int _currentNode = 0;
 	int _dialogMode = 0;
 	int _dialogShould = 0;
+	// Set when the 102/103 animation chain has been parked, so that a Rip
+	// state assigned from outside the chain can restart it.
+	bool _dialogChainIdle = false;
 	int _wolfMode = 0;
 	int _wolfShould = 0;
 	// Set when the 110/111 animation chain has been parked, so that a Wolf
@@ -77,6 +80,7 @@ private:
 	Common::String _sound2;
 
 	void kickWolfChain();
+	void kickDialogChain();
 	void conv402a();
 	void conv402a777();
 	void useTopiary();


Commit: 1ce0e58963b66f7cd2d329727dfa2c18e7f53bc3
    https://github.com/scummvm/scummvm/commit/1ce0e58963b66f7cd2d329727dfa2c18e7f53bc3
Author: sloanext (46776957+sloanext at users.noreply.github.com)
Date: 2026-08-08T12:20:21+10:00

Commit Message:
M4: RIDDLE: Clock room 402's talk chain off Rip's own machine

sendWSMessage() only delivers a directed message if the target machine is
listening for it, and a machine listens only once it has finished the
sequence it is playing. A message that arrives earlier is discarded by
SearchMsgList() without a word - taking the return trigger with it.

State 1100 sent Rip's talker with trigger -1 and hung the 102/103 chain
off the shadow machine instead. SAFARI SHADOW 3's frame completes sooner
than RIP TREK TALKER POS3's, so the chain reached 1103 and programmed Rip
about two frames before his machine went idle. That message was dropped,
the only trigger still outstanding on Rip was the -1 from 1100, and the
chain was dead from the first exchange of the conversation - with
_dialogChainIdle still false, because it had been killed rather than
parked.

So Rip never animated while speaking, and when the conversation ended
trigger 101 requested the teardown state 1105 while kickDialogChain()
correctly refused to restart a chain it believed was running. Nothing
unhid the walker or re-enabled the commands, and the game sat there with
the hourglass cursor.

Put the return trigger on the machine the chain is about to program next
and give the shadow the -1, so each message is only ever sent when its
machine has just reported idle. That is the idiom the rest of the room
already follows, state 2240 included.

State 1110 has the same shape - both machines get -1 and a timer advances
the chain, so 1115 would program a busy machine - and is reachable from
trigger 218 after paying Wolf. Fixed the same way; its timer is now
redundant.

Assisted-by: Claude:claude-opus-5

Changed paths:
    engines/m4/riddle/rooms/section4/room402.cpp


diff --git a/engines/m4/riddle/rooms/section4/room402.cpp b/engines/m4/riddle/rooms/section4/room402.cpp
index b9fe36e5301..9e11d5c9869 100644
--- a/engines/m4/riddle/rooms/section4/room402.cpp
+++ b/engines/m4/riddle/rooms/section4/room402.cpp
@@ -453,9 +453,19 @@ void Room402::daemon() {
 					_G(player_info).x, _G(player_info).y, _G(player_info).scale, 0x100, false,
 					triggerMachineByHashCallback, "rip talks wolf SHADOW");
 
-				sendWSMessage_10000(1, _ripEnterLeave, _ripTalker, 1, 1, -1,
+				// The return trigger has to ride on Rip's machine, not on the
+				// shadow. sendWSMessage() only delivers to a machine that is
+				// listening for the message, and a machine listens only once
+				// it has finished the sequence it is playing - a send that
+				// arrives earlier is discarded silently, return trigger and
+				// all. Driving the chain off the shadow meant 1103 programmed
+				// Rip two frames later, while his talker was still playing, so
+				// the message was dropped and nothing ever dispatched 102
+				// again. The chain has to be clocked by the machine it is
+				// about to program.
+				sendWSMessage_10000(1, _ripEnterLeave, _ripTalker, 1, 1, 102,
 					_ripTalker, 1, 1, 0);
-				sendWSMessage_10000(1, _safariShadow, _shadow3, 1, 1, 102,
+				sendWSMessage_10000(1, _safariShadow, _shadow3, 1, 1, -1,
 					_shadow3, 1, 1, 0);
 				_dialogShould = 1101;
 				_wolfMode = 2000;
@@ -599,7 +609,10 @@ void Room402::daemon() {
 					_shadow3, 1, 1, 0);
 				ws_hide_walker();
 
-				sendWSMessage_10000(1, _ripEnterLeave, _ripTalker, 1, 1, -1,
+				// Clock the chain off Rip's machine, as in state 1100 - a
+				// timer would fire while the talker is still playing and 1115
+				// would then be programming a machine that is not listening
+				sendWSMessage_10000(1, _ripEnterLeave, _ripTalker, 1, 1, 102,
 					_ripTalker, 1, 1, 0);
 				player_set_commands_allowed(false);
 				_wolfMode = 2002;
@@ -607,7 +620,6 @@ void Room402::daemon() {
 				kernel_timing_trigger(1, 110);
 
 				_dialogShould = 1111;
-				kernel_timing_trigger(1, 102);
 				break;
 
 			case 1111:




More information about the Scummvm-git-logs mailing list