@@ -135,12 +135,9 @@ def _layer_python() -> None:
135135 else :
136136 _record ("Layer 1" , "TCP socket bind" , False , "ports 8765-8770 all blocked" )
137137
138- # Daemon thread
139- flag = {"ok" : False }
140- def _worker (): flag ["ok" ] = True
141- t = threading .Thread (target = _worker , daemon = True )
142- t .start (); t .join (timeout = 2 )
143- _record ("Layer 1" , "Daemon thread" , flag ["ok" ])
138+ # Daemon thread class available (creation/join removed — t.join pumps Windows messages
139+ # via WaitForMultipleObjectsEx, which can fire pending Slate callbacks mid-test → crash)
140+ _record ("Layer 1" , "threading.Thread available" , hasattr (threading , "Thread" ))
144141
145142 # HTTP server class instantiable (daemon thread round-trip removed — unsafe in UEFN Slate tick)
146143 try :
@@ -179,39 +176,32 @@ def _layer_uefn() -> None:
179176 has_tick = hasattr (unreal , "register_slate_post_tick_callback" )
180177 _record ("Layer 2" , "register_slate_post_tick_callback" , has_tick )
181178
182- # Key subsystems
179+ # Key subsystems — hasattr only, no get_editor_subsystem() call.
180+ # Calling get_editor_subsystem() with a class not registered in UEFN writes to null at C++ level,
181+ # which Python try/except cannot catch. StaticMeshEditorSubsystem in particular may not exist in UEFN.
183182 for name in ["EditorActorSubsystem" , "EditorAssetSubsystem" ,
184183 "LevelEditorSubsystem" , "StaticMeshEditorSubsystem" ]:
185- try :
186- sub = unreal .get_editor_subsystem (getattr (unreal , name ))
187- _record ("Layer 2" , name , sub is not None )
188- except Exception as e :
189- _record ("Layer 2" , name , False , str (e ))
184+ ok = hasattr (unreal , name )
185+ _record ("Layer 2" , name , ok )
190186
191- # Key libraries
187+ # Key libraries — attribute existence only
192188 for lib in ["EditorAssetLibrary" , "EditorLevelLibrary" ,
193189 "EditorUtilityLibrary" , "MaterialEditingLibrary" ,
194190 "AutomationLibrary" ]:
195191 ok = hasattr (unreal , lib )
196192 _record ("Layer 2" , lib , ok )
197193
198- # AutomationLibrary.take_high_res_screenshot signature check
199- try :
200- fn = getattr (unreal .AutomationLibrary , "take_high_res_screenshot" , None )
201- _record ("Layer 2" , "AutomationLibrary.take_high_res_screenshot" , fn is not None )
202- except Exception as e :
203- _record ("Layer 2" , "AutomationLibrary.take_high_res_screenshot" , False , str (e ))
194+ # AutomationLibrary method check — attribute only, no call
195+ ok = hasattr (unreal , "AutomationLibrary" ) and \
196+ hasattr (unreal .AutomationLibrary , "take_high_res_screenshot" )
197+ _record ("Layer 2" , "AutomationLibrary.take_high_res_screenshot" , ok )
204198
205- # Saved dir writable
199+ # Saved dir path available (no write — Paths.project_saved_dir() is safe)
206200 try :
207201 saved = os .path .join (unreal .Paths .project_saved_dir (), "UEFN_Toolbelt" )
208- os .makedirs (saved , exist_ok = True )
209- probe = os .path .join (saved , "_smoke.tmp" )
210- with open (probe , "w" ) as f : f .write ("ok" )
211- os .remove (probe )
212- _record ("Layer 2" , "Saved/UEFN_Toolbelt/ writable" , True , saved )
202+ _record ("Layer 2" , "Paths.project_saved_dir()" , True , saved )
213203 except Exception as e :
214- _record ("Layer 2" , "Saved/UEFN_Toolbelt/ writable " , False , str (e ))
204+ _record ("Layer 2" , "Paths.project_saved_dir() " , False , str (e ))
215205
216206
217207# ─── Layer 3: Toolbelt core ───────────────────────────────────────────────────
@@ -268,50 +258,15 @@ def _layer_toolbelt() -> None:
268258 except Exception as e :
269259 _record ("Layer 3" , "tb.run() returns values" , False , str (e ))
270260
271- # Key tools explicitly registered
261+ # Key tools explicitly registered — registry lookup only, no execution.
262+ # Tool execution is the integration test's job (tb.run("toolbelt_integration_test")).
263+ # Running tools here risks Asset Registry crashes (Quirk #32), Slate callback
264+ # re-registration, and Qt module-level side effects — all can write to null in UEFN.
272265 for tool_name in ["verse_gen_custom" , "snapshot_save" , "material_apply_preset" ,
273266 "mcp_start" , "scatter_hism" , "tag_add" ]:
274267 ok = tool_name in tb .registry
275268 _record ("Layer 3" , f"tool: { tool_name } " , ok )
276269
277- # Execute Safe Tools (No Actor Needed)
278- safe_tools_to_test = [
279- # Core utilities
280- "api_list_subsystems" ,
281- "api_search" ,
282- "config_list" ,
283- "config_get" ,
284- "mcp_status" ,
285- # mcp_restart intentionally excluded — registers Slate tick callbacks mid-test → crash
286- "plugin_export_manifest" ,
287- "plugin_validate_all" ,
288- "plugin_list_custom" ,
289- # Scaffold / project
290- "scaffold_list_templates" ,
291- # Snapshots
292- "snapshot_list" ,
293- # Materials
294- "material_list_presets" ,
295- # Text
296- "text_list_styles" ,
297- # Theme
298- "theme_list" ,
299- "theme_get" ,
300- # Verse
301- "verse_list_snippets" ,
302- "verse_graph_scan" ,
303- # Measurement
304- "spline_measure" ,
305- # LOD / memory (read-only scans)
306- "lod_audit_folder" ,
307- ]
308- for safe_tool in safe_tools_to_test :
309- try :
310- tb .run (safe_tool )
311- _record ("Layer 3" , f"Execute { safe_tool } " , True )
312- except Exception as e :
313- _record ("Layer 3" , f"Execute { safe_tool } " , False , str (e ))
314-
315270
316271# ─── Layer 4: MCP bridge ──────────────────────────────────────────────────────
317272
0 commit comments