@@ -112,6 +112,13 @@ private void ProcessBlock(CFGBlock block, PipelineContext context)
112112 private BlueprintNode ? ProcessStatement ( CFGStatement stmt , string blockName ,
113113 PipelineContext context , ref BlueprintNode ? prevNode , ref string ? prevStmtId )
114114 {
115+ // PluginCallWithTarget must become an explicit CallNode carrying TargetDevice.
116+ // Route it before the registry dispatch — otherwise the registry creates a
117+ // BuiltinFunctionNode that ConfigureNode (which expects a CallNode) cannot convert,
118+ // leaving TargetDevice unset and producing no CallNode for the test to find.
119+ if ( stmt . FunctionName == "PluginCallWithTarget" )
120+ return ProcessCallOrAssignment ( stmt , context , ref prevNode , ref prevStmtId ) ;
121+
115122 // Registry path: handle all registered builtin functions
116123 if ( _functionRegistry != null && ! string . IsNullOrEmpty ( stmt . FunctionName ) )
117124 {
@@ -203,7 +210,28 @@ private bool IsRegistryFlowControlTerminator(CFGStatement stmt)
203210
204211 // --- Create new node ---
205212 BlueprintNode mainNode ;
206- if ( _functionRegistry != null && _functionRegistry . Get ( stmt . FunctionName ! ) is { } funcDef )
213+ if ( stmt . FunctionName == "PluginCallWithTarget" )
214+ {
215+ // PluginCallWithTarget must be an explicit CallNode carrying TargetDevice.
216+ // Handle BEFORE the builtin registry dispatch — otherwise the registry shadows
217+ // it (PluginCallWithTarget is a registered builtin) and TargetDevice is never set.
218+ var callNode = ( CallNode ) _registry . Create ( BlueprintNodeType . Call ) ;
219+ var args = stmt . Arguments ;
220+ // G.PluginCallWithTarget("plugin", "method", "device", ...)
221+ // Arguments[0]=pluginName, [1]=methodName, [2]=targetDevice
222+ callNode . PluginName = args ? . Count > 0 ? StripQuotes ( args [ 0 ] ) : "" ;
223+ callNode . FunctionName = args ? . Count > 1 ? StripQuotes ( args [ 1 ] ) : "" ;
224+ callNode . TargetDevice = args ? . Count > 2 ? StripQuotes ( args [ 2 ] ) : null ;
225+ // Preserve extra arguments (beyond plugin/method/device) for BS round-trip,
226+ // mirroring PluginCallWithTargetFunction.ConfigureNode.
227+ if ( args != null && args . Count > 3 )
228+ callNode . ExtraArguments = args . Skip ( 3 ) . ToList ( ) ;
229+ mainNode = callNode ;
230+
231+ // Add parameter pins based on argument count
232+ AddParamPins ( mainNode , stmt . FunctionName ! , stmt . Arguments ? . Count ?? 0 ) ;
233+ }
234+ else if ( _functionRegistry != null && _functionRegistry . Get ( stmt . FunctionName ! ) is { } funcDef )
207235 {
208236 mainNode = _registry . CreateBuiltinFunctionNode ( stmt . FunctionName ! ) ;
209237 mainNode = funcDef . ConfigureNode ( mainNode , stmt ) ;
@@ -222,17 +250,7 @@ private bool IsRegistryFlowControlTerminator(CFGStatement stmt)
222250 var callNode = ( CallNode ) _registry . Create ( BlueprintNodeType . Call ) ;
223251
224252 // Parse plugin name from full dotted method name (e.g. "TestPlugin.WPF.Core.HelloKitX")
225- // PluginCallWithTarget has G.PluginCallWithTarget as FullFunctionName, handle it first
226- if ( stmt . FunctionName == "PluginCallWithTarget" )
227- {
228- // G.PluginCallWithTarget("plugin", "method", "device", ...)
229- // Arguments[0]=pluginName, [1]=methodName, [2]=targetDevice
230- var args = stmt . Arguments ;
231- callNode . PluginName = args ? . Count > 0 ? StripQuotes ( args [ 0 ] ) : "" ;
232- callNode . FunctionName = args ? . Count > 1 ? StripQuotes ( args [ 1 ] ) : "" ;
233- callNode . TargetDevice = args ? . Count > 2 ? StripQuotes ( args [ 2 ] ) : null ;
234- }
235- else if ( ! string . IsNullOrEmpty ( stmt . FullFunctionName ) && stmt . FullFunctionName . Contains ( '.' ) )
253+ if ( ! string . IsNullOrEmpty ( stmt . FullFunctionName ) && stmt . FullFunctionName . Contains ( '.' ) )
236254 {
237255 var lastDot = stmt . FullFunctionName . LastIndexOf ( '.' ) ;
238256 callNode . PluginName = stmt . FullFunctionName . Substring ( 0 , lastDot ) ;
0 commit comments