diff --git a/Assets/Editor/InteractionSystem.meta b/Assets/Editor/InteractionSystem.meta
new file mode 100644
index 00000000..65748ff5
--- /dev/null
+++ b/Assets/Editor/InteractionSystem.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: cb41c852d70c4066bf510792ee19b3f5
+timeCreated: 1762866335
\ No newline at end of file
diff --git a/Assets/Editor/InteractionSystem/InteractableEditorWindow.cs b/Assets/Editor/InteractionSystem/InteractableEditorWindow.cs
new file mode 100644
index 00000000..923b1336
--- /dev/null
+++ b/Assets/Editor/InteractionSystem/InteractableEditorWindow.cs
@@ -0,0 +1,748 @@
+using System.Collections.Generic;
+using System.Linq;
+using UnityEditor;
+using UnityEngine;
+using Interactions;
+using System.Reflection;
+using System;
+
+namespace AppleHills.Editor.InteractionSystem
+{
+ ///
+ /// Editor utility for managing and debugging interactable objects in the scene.
+ /// Provides scene object locator, inspector editing, and runtime debugging capabilities.
+ ///
+ public class InteractableEditorWindow : EditorWindow
+ {
+ // Tab management
+ private int _selectedTab = 0;
+ private readonly string[] _tabNames = { "Scene", "Debug" };
+
+ // Scene interactables tracking
+ private List _sceneInteractables = new List();
+ private InteractableBase _selectedInteractable;
+ private GameObject _selectedGameObject;
+
+ // UI state
+ private Vector2 _listScrollPosition;
+ private Vector2 _inspectorScrollPosition;
+ private Vector2 _debugScrollPosition;
+ private string _searchQuery = "";
+
+ // Runtime state
+ private bool _isPlaying = false;
+
+ // Editor for selected interactable
+ private UnityEditor.Editor _cachedEditor;
+
+ // Available interactable types for adding
+ private static readonly Type[] AvailableInteractableTypes = new Type[]
+ {
+ typeof(OneClickInteraction),
+ typeof(Pickup),
+ typeof(ItemSlot),
+ typeof(SaveableInteractable),
+ typeof(InteractableBase)
+ };
+
+ [MenuItem("AppleHills/Interactable Editor")]
+ public static void ShowWindow()
+ {
+ var window = GetWindow("Interactable Editor");
+ window.minSize = new Vector2(900, 600);
+ window.Show();
+ }
+
+ private void OnEnable()
+ {
+ RefreshSceneInteractables();
+
+ // Register for scene and selection changes
+ UnityEditor.SceneManagement.EditorSceneManager.sceneOpened += OnSceneOpened;
+ UnityEditor.SceneManagement.EditorSceneManager.sceneClosed += OnSceneClosed;
+ Selection.selectionChanged += OnSelectionChanged;
+ EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
+ EditorApplication.hierarchyChanged += OnHierarchyChanged;
+ }
+
+ private void OnDisable()
+ {
+ UnityEditor.SceneManagement.EditorSceneManager.sceneOpened -= OnSceneOpened;
+ UnityEditor.SceneManagement.EditorSceneManager.sceneClosed -= OnSceneClosed;
+ Selection.selectionChanged -= OnSelectionChanged;
+ EditorApplication.playModeStateChanged -= OnPlayModeStateChanged;
+ EditorApplication.hierarchyChanged -= OnHierarchyChanged;
+
+ // Clean up cached editor
+ if (_cachedEditor != null)
+ {
+ DestroyImmediate(_cachedEditor);
+ _cachedEditor = null;
+ }
+ }
+
+ private void OnSceneOpened(UnityEngine.SceneManagement.Scene scene, UnityEditor.SceneManagement.OpenSceneMode mode)
+ {
+ RefreshSceneInteractables();
+ }
+
+ private void OnSceneClosed(UnityEngine.SceneManagement.Scene scene)
+ {
+ RefreshSceneInteractables();
+ }
+
+ private void OnHierarchyChanged()
+ {
+ RefreshSceneInteractables();
+ }
+
+ private void OnSelectionChanged()
+ {
+ // Check if selected object has changed
+ if (Selection.activeGameObject != null && Selection.activeGameObject != _selectedGameObject)
+ {
+ var interactable = Selection.activeGameObject.GetComponent();
+ if (interactable != null)
+ {
+ // GameObject has an interactable - select it
+ SelectInteractable(interactable);
+ }
+ else
+ {
+ // GameObject doesn't have an interactable - track it for add menu
+ _selectedGameObject = Selection.activeGameObject;
+ _selectedInteractable = null;
+
+ // Clear cached editor
+ if (_cachedEditor != null)
+ {
+ DestroyImmediate(_cachedEditor);
+ _cachedEditor = null;
+ }
+ }
+ Repaint();
+ }
+ else if (Selection.activeGameObject == null)
+ {
+ // Nothing selected - clear selection
+ _selectedGameObject = null;
+ _selectedInteractable = null;
+
+ if (_cachedEditor != null)
+ {
+ DestroyImmediate(_cachedEditor);
+ _cachedEditor = null;
+ }
+ Repaint();
+ }
+ }
+
+ private void OnPlayModeStateChanged(PlayModeStateChange state)
+ {
+ _isPlaying = EditorApplication.isPlaying;
+ if (_isPlaying)
+ {
+ RefreshSceneInteractables();
+ }
+ Repaint();
+ }
+
+ private void OnGUI()
+ {
+ DrawHeader();
+
+ _selectedTab = GUILayout.Toolbar(_selectedTab, _tabNames);
+
+ EditorGUILayout.Space();
+
+ switch (_selectedTab)
+ {
+ case 0: // Scene tab
+ DrawSceneTab();
+ break;
+ case 1: // Debug tab
+ DrawDebugTab();
+ break;
+ }
+ }
+
+ #region Header UI
+
+ private void DrawHeader()
+ {
+ EditorGUILayout.BeginHorizontal(EditorStyles.toolbar);
+
+ if (GUILayout.Button("Refresh", EditorStyles.toolbarButton, GUILayout.Width(60)))
+ {
+ RefreshSceneInteractables();
+ }
+
+ GUILayout.FlexibleSpace();
+
+ // Tab-specific toolbar options
+ if (_selectedTab == 0) // Scene tab
+ {
+ EditorGUILayout.LabelField($"Found: {_sceneInteractables.Count} interactables", EditorStyles.toolbarButton, GUILayout.Width(150));
+ }
+ else if (_selectedTab == 1) // Debug tab
+ {
+ EditorGUILayout.LabelField(_isPlaying ? "Runtime Active" : "Editor Mode", EditorStyles.toolbarButton, GUILayout.Width(100));
+ }
+
+ EditorGUILayout.EndHorizontal();
+ }
+
+ #endregion
+
+ #region Scene Tab
+
+ private void DrawSceneTab()
+ {
+ EditorGUILayout.BeginHorizontal();
+
+ // Left panel - interactable list
+ EditorGUILayout.BeginVertical(GUILayout.Width(300));
+ DrawInteractableListPanel();
+ EditorGUILayout.EndVertical();
+
+ // Separator
+ EditorGUILayout.Space(5);
+
+ // Right panel - inspector/editor
+ EditorGUILayout.BeginVertical();
+ DrawInspectorPanel();
+ EditorGUILayout.EndVertical();
+
+ EditorGUILayout.EndHorizontal();
+ }
+
+ private void DrawInteractableListPanel()
+ {
+ // Search field
+ EditorGUILayout.BeginHorizontal(EditorStyles.toolbar);
+ _searchQuery = EditorGUILayout.TextField(_searchQuery, EditorStyles.toolbarSearchField);
+ if (GUILayout.Button("×", EditorStyles.toolbarButton, GUILayout.Width(20)) && !string.IsNullOrEmpty(_searchQuery))
+ {
+ _searchQuery = "";
+ GUI.FocusControl(null);
+ }
+ EditorGUILayout.EndHorizontal();
+
+ _listScrollPosition = EditorGUILayout.BeginScrollView(_listScrollPosition);
+
+ // Filter interactables by search query
+ var filteredInteractables = string.IsNullOrEmpty(_searchQuery)
+ ? _sceneInteractables
+ : _sceneInteractables.Where(i => i != null && i.gameObject.name.ToLower().Contains(_searchQuery.ToLower())).ToList();
+
+ if (filteredInteractables.Count == 0)
+ {
+ EditorGUILayout.HelpBox("No interactables found in scene", MessageType.Info);
+ }
+ else
+ {
+ foreach (var interactable in filteredInteractables)
+ {
+ if (interactable == null) continue;
+
+ if (DrawInteractableListItem(interactable))
+ {
+ SelectInteractable(interactable);
+ }
+ }
+ }
+
+ EditorGUILayout.EndScrollView();
+ }
+
+ private bool DrawInteractableListItem(InteractableBase interactable)
+ {
+ bool isSelected = interactable == _selectedInteractable;
+
+ Color originalColor = GUI.backgroundColor;
+ if (isSelected)
+ GUI.backgroundColor = new Color(0.3f, 0.5f, 0.8f);
+
+ EditorGUILayout.BeginVertical(EditorStyles.helpBox);
+ GUI.backgroundColor = originalColor;
+
+ EditorGUILayout.BeginHorizontal();
+
+ // Interactable info
+ EditorGUILayout.BeginVertical();
+ EditorGUILayout.LabelField(interactable.gameObject.name, EditorStyles.boldLabel);
+ EditorGUILayout.LabelField(interactable.GetType().Name, EditorStyles.miniLabel);
+
+ // Show additional info for specific types
+ if (interactable is Pickup pickup && pickup.itemData != null)
+ {
+ EditorGUILayout.LabelField($"Item: {pickup.itemData.itemName}", EditorStyles.miniLabel);
+ }
+ else if (interactable is ItemSlot slot && slot.itemData != null)
+ {
+ EditorGUILayout.LabelField($"Slot: {slot.itemData.itemName}", EditorStyles.miniLabel);
+ }
+
+ EditorGUILayout.EndVertical();
+
+ GUILayout.FlexibleSpace();
+
+ // Ping button
+ if (GUILayout.Button("Ping", GUILayout.Width(50)))
+ {
+ EditorGUIUtility.PingObject(interactable.gameObject);
+ Selection.activeGameObject = interactable.gameObject;
+ }
+
+ EditorGUILayout.EndHorizontal();
+ EditorGUILayout.EndVertical();
+
+ Rect itemRect = GUILayoutUtility.GetLastRect();
+ bool wasClicked = Event.current.type == EventType.MouseDown && Event.current.button == 0
+ && itemRect.Contains(Event.current.mousePosition);
+
+ if (wasClicked)
+ {
+ Event.current.Use();
+ }
+
+ return wasClicked;
+ }
+
+ private void DrawInspectorPanel()
+ {
+ _inspectorScrollPosition = EditorGUILayout.BeginScrollView(_inspectorScrollPosition);
+
+ if (_selectedInteractable == null && _selectedGameObject == null)
+ {
+ EditorGUILayout.HelpBox("Select an interactable from the list or in the scene hierarchy", MessageType.Info);
+ }
+ else if (_selectedGameObject != null && _selectedInteractable == null)
+ {
+ // Selected object doesn't have an interactable - show add menu
+ DrawAddInteractableMenu();
+ }
+ else if (_selectedInteractable != null)
+ {
+ // Draw custom inspector for the selected interactable
+ DrawInteractableInspector();
+ }
+
+ EditorGUILayout.EndScrollView();
+ }
+
+ private void DrawAddInteractableMenu()
+ {
+ EditorGUILayout.HelpBox($"GameObject '{_selectedGameObject.name}' doesn't have an Interactable component", MessageType.Info);
+
+ EditorGUILayout.Space();
+ EditorGUILayout.LabelField("Add Interactable Component:", EditorStyles.boldLabel);
+
+ foreach (var interactableType in AvailableInteractableTypes)
+ {
+ if (GUILayout.Button($"Add {interactableType.Name}", GUILayout.Height(30)))
+ {
+ Undo.RecordObject(_selectedGameObject, $"Add {interactableType.Name}");
+ var component = _selectedGameObject.AddComponent(interactableType) as InteractableBase;
+ EditorUtility.SetDirty(_selectedGameObject);
+ SelectInteractable(component);
+ RefreshSceneInteractables();
+ }
+ }
+ }
+
+ private void DrawInteractableInspector()
+ {
+ // Header
+ EditorGUILayout.BeginHorizontal();
+ EditorGUILayout.LabelField("Editing:", EditorStyles.boldLabel, GUILayout.Width(60));
+ EditorGUILayout.LabelField(_selectedInteractable.gameObject.name, EditorStyles.boldLabel);
+
+ GUILayout.FlexibleSpace();
+
+ if (GUILayout.Button("Ping", GUILayout.Width(50)))
+ {
+ EditorGUIUtility.PingObject(_selectedInteractable.gameObject);
+ Selection.activeGameObject = _selectedInteractable.gameObject;
+ }
+
+ EditorGUILayout.EndHorizontal();
+
+ EditorGUILayout.Space();
+
+ // Draw default inspector using Editor
+ if (_cachedEditor == null || _cachedEditor.target != _selectedInteractable)
+ {
+ if (_cachedEditor != null)
+ {
+ DestroyImmediate(_cachedEditor);
+ }
+ _cachedEditor = UnityEditor.Editor.CreateEditor(_selectedInteractable);
+ }
+
+ if (_cachedEditor != null)
+ {
+ EditorGUI.BeginChangeCheck();
+ _cachedEditor.OnInspectorGUI();
+ if (EditorGUI.EndChangeCheck())
+ {
+ EditorUtility.SetDirty(_selectedInteractable);
+ }
+ }
+
+ EditorGUILayout.Space();
+
+ // Additional info section
+ DrawAdditionalInfo();
+ }
+
+ private void DrawAdditionalInfo()
+ {
+ EditorGUILayout.LabelField("Additional Information", EditorStyles.boldLabel);
+ EditorGUILayout.BeginVertical(EditorStyles.helpBox);
+
+ // Show component information
+ EditorGUILayout.LabelField("Type:", _selectedInteractable.GetType().Name);
+
+ // Show attached actions
+ var actions = _selectedInteractable.GetComponents();
+ if (actions.Length > 0)
+ {
+ EditorGUILayout.LabelField($"Actions: {actions.Length}");
+ EditorGUI.indentLevel++;
+ foreach (var action in actions)
+ {
+ EditorGUILayout.BeginHorizontal();
+ EditorGUILayout.LabelField(action.GetType().Name, EditorStyles.miniLabel);
+ if (GUILayout.Button("Ping", GUILayout.Width(50)))
+ {
+ EditorGUIUtility.PingObject(action);
+ }
+ EditorGUILayout.EndHorizontal();
+ }
+ EditorGUI.indentLevel--;
+ }
+
+ // Show specific type info
+ if (_selectedInteractable is Pickup pickup)
+ {
+ EditorGUILayout.Space();
+ EditorGUILayout.LabelField("Pickup Info:", EditorStyles.boldLabel);
+ if (pickup.itemData != null)
+ {
+ EditorGUILayout.BeginHorizontal();
+ EditorGUILayout.LabelField("Item Data:", GUILayout.Width(100));
+ EditorGUILayout.ObjectField(pickup.itemData, typeof(PickupItemData), false);
+ if (GUILayout.Button("Ping", GUILayout.Width(50)))
+ {
+ EditorGUIUtility.PingObject(pickup.itemData);
+ }
+ EditorGUILayout.EndHorizontal();
+ }
+ }
+ else if (_selectedInteractable is ItemSlot slot)
+ {
+ EditorGUILayout.Space();
+ EditorGUILayout.LabelField("Slot Info:", EditorStyles.boldLabel);
+ if (slot.itemData != null)
+ {
+ EditorGUILayout.BeginHorizontal();
+ EditorGUILayout.LabelField("Slot Data:", GUILayout.Width(100));
+ EditorGUILayout.ObjectField(slot.itemData, typeof(PickupItemData), false);
+ if (GUILayout.Button("Ping", GUILayout.Width(50)))
+ {
+ EditorGUIUtility.PingObject(slot.itemData);
+ }
+ EditorGUILayout.EndHorizontal();
+ }
+
+ if (_isPlaying)
+ {
+ var slottedObject = slot.GetSlottedObject();
+ if (slottedObject != null)
+ {
+ EditorGUILayout.BeginHorizontal();
+ EditorGUILayout.LabelField("Slotted Object:", GUILayout.Width(100));
+ EditorGUILayout.ObjectField(slottedObject, typeof(GameObject), true);
+ if (GUILayout.Button("Ping", GUILayout.Width(50)))
+ {
+ EditorGUIUtility.PingObject(slottedObject);
+ }
+ EditorGUILayout.EndHorizontal();
+ }
+ }
+ }
+
+ EditorGUILayout.EndVertical();
+ }
+
+ #endregion
+
+ #region Debug Tab
+
+ private void DrawDebugTab()
+ {
+ if (!_isPlaying)
+ {
+ EditorGUILayout.HelpBox("Enter Play Mode to debug interactables at runtime", MessageType.Info);
+ return;
+ }
+
+ _debugScrollPosition = EditorGUILayout.BeginScrollView(_debugScrollPosition);
+
+ EditorGUILayout.LabelField("Scene Interactables", EditorStyles.boldLabel);
+ EditorGUILayout.HelpBox("Test interactions and trigger individual events", MessageType.Info);
+
+ EditorGUILayout.Space();
+
+ foreach (var interactable in _sceneInteractables)
+ {
+ if (interactable == null) continue;
+
+ DrawDebugInteractableItem(interactable);
+ }
+
+ EditorGUILayout.EndScrollView();
+ }
+
+ private void DrawDebugInteractableItem(InteractableBase interactable)
+ {
+ EditorGUILayout.BeginVertical(EditorStyles.helpBox);
+
+ // Header
+ EditorGUILayout.BeginHorizontal();
+ EditorGUILayout.LabelField(interactable.gameObject.name, EditorStyles.boldLabel);
+ EditorGUILayout.LabelField($"({interactable.GetType().Name})", EditorStyles.miniLabel);
+
+ GUILayout.FlexibleSpace();
+
+ if (GUILayout.Button("Ping", GUILayout.Width(50)))
+ {
+ EditorGUIUtility.PingObject(interactable.gameObject);
+ Selection.activeGameObject = interactable.gameObject;
+ }
+
+ EditorGUILayout.EndHorizontal();
+
+ EditorGUILayout.Space();
+
+ // Interaction buttons
+ EditorGUILayout.LabelField("Trigger Interaction:", EditorStyles.boldLabel);
+
+ EditorGUILayout.BeginHorizontal();
+
+ if (GUILayout.Button("Full Interaction", GUILayout.Height(25)))
+ {
+ TriggerFullInteraction(interactable);
+ }
+
+ EditorGUILayout.EndHorizontal();
+
+ EditorGUILayout.Space();
+
+ // Event buttons
+ EditorGUILayout.LabelField("Trigger Individual Events:", EditorStyles.boldLabel);
+
+ EditorGUILayout.BeginHorizontal();
+
+ if (GUILayout.Button("Started"))
+ {
+ TriggerEvent(interactable, "OnInteractionStarted");
+ TriggerUnityEvent(interactable, "interactionStarted");
+ }
+
+ if (GUILayout.Button("Arrived"))
+ {
+ TriggerEvent(interactable, "OnInteractingCharacterArrived");
+ TriggerUnityEvent(interactable, "characterArrived");
+ }
+
+ if (GUILayout.Button("Do Interaction"))
+ {
+ TriggerEvent(interactable, "DoInteraction");
+ }
+
+ EditorGUILayout.EndHorizontal();
+
+ EditorGUILayout.BeginHorizontal();
+
+ if (GUILayout.Button("Complete (Success)"))
+ {
+ TriggerEventWithParam(interactable, "OnInteractionFinished", true);
+ TriggerUnityEventWithParam(interactable, "interactionComplete", true);
+ }
+
+ if (GUILayout.Button("Complete (Fail)"))
+ {
+ TriggerEventWithParam(interactable, "OnInteractionFinished", false);
+ TriggerUnityEventWithParam(interactable, "interactionComplete", false);
+ }
+
+ if (GUILayout.Button("Interrupted"))
+ {
+ TriggerUnityEvent(interactable, "interactionInterrupted");
+ }
+
+ EditorGUILayout.EndHorizontal();
+
+ // Show registered actions
+ var actions = interactable.GetComponents();
+ if (actions.Length > 0)
+ {
+ EditorGUILayout.Space();
+ EditorGUILayout.LabelField($"Registered Actions ({actions.Length}):", EditorStyles.boldLabel);
+
+ foreach (var action in actions)
+ {
+ EditorGUILayout.BeginHorizontal(EditorStyles.helpBox);
+ EditorGUILayout.LabelField(action.GetType().Name);
+
+ if (action.respondToEvents != null && action.respondToEvents.Count > 0)
+ {
+ string events = string.Join(", ", action.respondToEvents);
+ EditorGUILayout.LabelField($"Events: {events}", EditorStyles.miniLabel);
+ }
+
+ EditorGUILayout.EndHorizontal();
+ }
+ }
+
+ EditorGUILayout.EndVertical();
+ EditorGUILayout.Space();
+ }
+
+ #endregion
+
+ #region Data Management
+
+ private void RefreshSceneInteractables()
+ {
+ _sceneInteractables.Clear();
+
+ // Find all interactables in the scene
+ var allInteractables = FindObjectsByType(FindObjectsSortMode.None);
+ _sceneInteractables.AddRange(allInteractables);
+
+ // Sort by name for easier browsing
+ _sceneInteractables.Sort((a, b) =>
+ {
+ if (a == null || b == null) return 0;
+ return string.Compare(a.gameObject.name, b.gameObject.name, StringComparison.Ordinal);
+ });
+ }
+
+ private void SelectInteractable(InteractableBase interactable)
+ {
+ _selectedInteractable = interactable;
+ _selectedGameObject = interactable?.gameObject;
+
+ // Clear cached editor to force recreation
+ if (_cachedEditor != null)
+ {
+ DestroyImmediate(_cachedEditor);
+ _cachedEditor = null;
+ }
+ }
+
+ #endregion
+
+ #region Debug Helpers
+
+ private void TriggerFullInteraction(InteractableBase interactable)
+ {
+ if (!_isPlaying || interactable == null) return;
+
+ // Simulate a tap on the interactable
+ Vector3 worldPos = interactable.transform.position;
+ interactable.OnTap(new Vector2(worldPos.x, worldPos.y));
+
+ Debug.Log($"[Interactable Editor] Triggered full interaction on {interactable.gameObject.name}");
+ }
+
+ private void TriggerEvent(InteractableBase interactable, string methodName)
+ {
+ if (!_isPlaying || interactable == null) return;
+
+ Type type = interactable.GetType();
+ MethodInfo method = type.GetMethod(methodName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
+
+ if (method != null)
+ {
+ try
+ {
+ method.Invoke(interactable, null);
+ Debug.Log($"[Interactable Editor] Invoked {methodName} on {interactable.gameObject.name}");
+ }
+ catch (Exception e)
+ {
+ Debug.LogError($"[Interactable Editor] Error invoking {methodName}: {e.Message}");
+ }
+ }
+ else
+ {
+ Debug.LogWarning($"[Interactable Editor] Method {methodName} not found on {type.Name}");
+ }
+ }
+
+ private void TriggerEventWithParam(InteractableBase interactable, string methodName, object param)
+ {
+ if (!_isPlaying || interactable == null) return;
+
+ Type type = interactable.GetType();
+ MethodInfo method = type.GetMethod(methodName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
+
+ if (method != null)
+ {
+ try
+ {
+ method.Invoke(interactable, new object[] { param });
+ Debug.Log($"[Interactable Editor] Invoked {methodName}({param}) on {interactable.gameObject.name}");
+ }
+ catch (Exception e)
+ {
+ Debug.LogError($"[Interactable Editor] Error invoking {methodName}: {e.Message}");
+ }
+ }
+ else
+ {
+ Debug.LogWarning($"[Interactable Editor] Method {methodName} not found on {type.Name}");
+ }
+ }
+
+ private void TriggerUnityEvent(InteractableBase interactable, string fieldName)
+ {
+ if (!_isPlaying || interactable == null) return;
+
+ Type type = interactable.GetType();
+ FieldInfo field = type.GetField(fieldName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
+
+ if (field != null && field.GetValue(interactable) is UnityEngine.Events.UnityEventBase unityEvent)
+ {
+ // Use reflection to invoke the protected Invoke method
+ MethodInfo invokeMethod = unityEvent.GetType().GetMethod("Invoke", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
+ if (invokeMethod != null)
+ {
+ invokeMethod.Invoke(unityEvent, null);
+ Debug.Log($"[Interactable Editor] Invoked UnityEvent {fieldName} on {interactable.gameObject.name}");
+ }
+ }
+ }
+
+ private void TriggerUnityEventWithParam(InteractableBase interactable, string fieldName, bool param)
+ {
+ if (!_isPlaying || interactable == null) return;
+
+ Type type = interactable.GetType();
+ FieldInfo field = type.GetField(fieldName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
+
+ if (field != null && field.GetValue(interactable) is UnityEngine.Events.UnityEvent unityEvent)
+ {
+ unityEvent.Invoke(param);
+ Debug.Log($"[Interactable Editor] Invoked UnityEvent {fieldName}({param}) on {interactable.gameObject.name}");
+ }
+ }
+
+ #endregion
+ }
+}
+
diff --git a/Assets/Editor/InteractionSystem/InteractableEditorWindow.cs.meta b/Assets/Editor/InteractionSystem/InteractableEditorWindow.cs.meta
new file mode 100644
index 00000000..1f438cd6
--- /dev/null
+++ b/Assets/Editor/InteractionSystem/InteractableEditorWindow.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 3045d5bcf3e04203bfe060f80d8913ca
+timeCreated: 1762866335
\ No newline at end of file
diff --git a/Assets/Editor/PuzzleSystem/PuzzleEditorWindow.cs b/Assets/Editor/PuzzleSystem/PuzzleEditorWindow.cs
index dac05fa0..50d87638 100644
--- a/Assets/Editor/PuzzleSystem/PuzzleEditorWindow.cs
+++ b/Assets/Editor/PuzzleSystem/PuzzleEditorWindow.cs
@@ -560,6 +560,15 @@ namespace AppleHills.Editor.PuzzleSystem
EditorGUILayout.BeginHorizontal(EditorStyles.toolbar);
EditorGUILayout.LabelField($"Current Level: {_runtimeLevelData.levelId}", EditorStyles.boldLabel);
+
+ GUILayout.FlexibleSpace();
+
+ // Unlock All button
+ if (GUILayout.Button("Unlock All", EditorStyles.toolbarButton, GUILayout.Width(100)))
+ {
+ UnlockAllPuzzles();
+ }
+
EditorGUILayout.EndHorizontal();
_debugScrollPosition = EditorGUILayout.BeginScrollView(_debugScrollPosition);
@@ -870,6 +879,121 @@ namespace AppleHills.Editor.PuzzleSystem
UpdateRuntimeData();
}
+ private void UnlockAllPuzzles()
+ {
+ if (!_isPlaying || _runtimeLevelData == null) return;
+
+ PuzzleManager puzzleManager = Object.FindFirstObjectByType();
+ if (puzzleManager == null)
+ {
+ Debug.LogError("[Puzzle Editor] Cannot find PuzzleManager in scene");
+ return;
+ }
+
+ Debug.Log("[Puzzle Editor] Starting to unlock all puzzles...");
+
+ // Get all steps from the level data
+ List allSteps = new List(_runtimeLevelData.allSteps);
+
+ // Track which steps we've processed
+ HashSet processedSteps = new HashSet();
+ bool madeProgress = true;
+ int maxIterations = 100; // Safety limit to prevent infinite loops
+ int iteration = 0;
+
+ // Keep iterating until no more steps can be unlocked/completed
+ while (madeProgress && iteration < maxIterations)
+ {
+ madeProgress = false;
+ iteration++;
+
+ foreach (var step in allSteps)
+ {
+ if (step == null || processedSteps.Contains(step.stepId))
+ continue;
+
+ // Check if already completed
+ if (puzzleManager.IsPuzzleStepCompleted(step.stepId))
+ {
+ processedSteps.Add(step.stepId);
+ continue;
+ }
+
+ // Check if step is unlocked or can be unlocked
+ bool isUnlocked = puzzleManager.IsStepUnlocked(step);
+
+ if (!isUnlocked)
+ {
+ // Try to unlock it if dependencies are met
+ // We need to check if all dependencies are completed
+ bool canUnlock = CanUnlockStep(step, puzzleManager);
+
+ if (canUnlock)
+ {
+ // Unlock the step using reflection
+ System.Type managerType = puzzleManager.GetType();
+ System.Reflection.MethodInfo unlockMethod = managerType.GetMethod("UnlockStep",
+ System.Reflection.BindingFlags.Instance |
+ System.Reflection.BindingFlags.Public |
+ System.Reflection.BindingFlags.NonPublic);
+
+ if (unlockMethod != null)
+ {
+ unlockMethod.Invoke(puzzleManager, new object[] { step });
+ Debug.Log($"[Puzzle Editor] Unlocked step: {step.stepId}");
+ isUnlocked = true;
+ }
+ }
+ }
+
+ // If unlocked, complete it
+ if (isUnlocked && !puzzleManager.IsPuzzleStepCompleted(step.stepId))
+ {
+ puzzleManager.MarkPuzzleStepCompleted(step);
+ Debug.Log($"[Puzzle Editor] Completed step: {step.stepId}");
+ processedSteps.Add(step.stepId);
+ madeProgress = true;
+ }
+ }
+ }
+
+ if (iteration >= maxIterations)
+ {
+ Debug.LogWarning($"[Puzzle Editor] Reached maximum iterations ({maxIterations}). Some steps may not have been completed.");
+ }
+
+ Debug.Log($"[Puzzle Editor] Unlock all complete. Processed {processedSteps.Count} steps in {iteration} iterations.");
+
+ // Update runtime data to reflect all changes
+ UpdateRuntimeData();
+ }
+
+ ///
+ /// Checks if a step can be unlocked by verifying all its dependencies are completed
+ ///
+ private bool CanUnlockStep(PuzzleStepSO step, PuzzleManager puzzleManager)
+ {
+ if (step == null || _runtimeLevelData == null) return false;
+
+ // Initial steps can always be unlocked
+ if (_runtimeLevelData.IsInitialStep(step))
+ return true;
+
+ // Check if all dependencies are completed
+ if (_runtimeLevelData.stepDependencies.TryGetValue(step.stepId, out string[] dependencies))
+ {
+ foreach (var depId in dependencies)
+ {
+ if (!puzzleManager.IsPuzzleStepCompleted(depId))
+ {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
#endregion
}
}
diff --git a/Assets/Prefabs/Puzzles/Picnic.prefab b/Assets/Prefabs/Puzzles/Picnic.prefab
index 9edb087b..aab47252 100644
--- a/Assets/Prefabs/Puzzles/Picnic.prefab
+++ b/Assets/Prefabs/Puzzles/Picnic.prefab
@@ -260,6 +260,7 @@ MonoBehaviour:
getFlirtyMin: 4
getFlirtyMax: 5
fakeChocolate: {fileID: 2391935521422290070}
+ realChocolate: {fileID: 0}
distractedAudioClips: {fileID: 6418180475301049370, guid: 956d8d84e8dd1de4e94ba48c041dc6ec, type: 2}
angryAudioClips: {fileID: 6418180475301049370, guid: 22e6e844862e5b94989b572cb70c1eff, type: 2}
feederClips: {fileID: 6418180475301049370, guid: 2e607d3f32c25a14ea074850dd2f8ac5, type: 2}
diff --git a/docs/interactables/code_reference.md b/docs/interactables/code_reference.md
new file mode 100644
index 00000000..5ebd36dc
--- /dev/null
+++ b/docs/interactables/code_reference.md
@@ -0,0 +1,573 @@
+# Interactables System - Code Reference
+
+## Table of Contents
+
+1. [Overview](#overview)
+2. [Class Hierarchy](#class-hierarchy)
+3. [InteractableBase - The Template Method](#interactablebase---the-template-method)
+ - [Interaction Flow](#interaction-flow)
+ - [Virtual Methods to Override](#virtual-methods-to-override)
+4. [Creating Custom Interactables](#creating-custom-interactables)
+ - [Example 1: Simple Button (OneClickInteraction)](#example-1-simple-button-oneclickinteraction)
+ - [Example 2: Item Pickup](#example-2-item-pickup)
+ - [Example 3: Item Slot with Validation](#example-3-item-slot-with-validation)
+5. [Character Movement](#character-movement)
+6. [Action Component System](#action-component-system)
+7. [Events System](#events-system)
+8. [Save/Load System Integration](#saveload-system-integration)
+9. [Integration with Puzzle System](#integration-with-puzzle-system)
+10. [Advanced Patterns](#advanced-patterns)
+
+---
+
+## Overview
+
+Simple, centrally orchestrated interaction system for player and follower characters.
+
+### Core Concepts
+
+- **Template Method Pattern**: `InteractableBase` defines the interaction flow; subclasses override specific steps
+- **Action Component System**: Modular actions respond to interaction events independently
+- **Async/Await Flow**: Character movement and timeline playback use async patterns
+- **Save/Load Integration**: `SaveableInteractable` provides persistence for interaction state
+
+---
+
+## Class Hierarchy
+
+```
+ManagedBehaviour
+ └── InteractableBase
+ ├── OneClickInteraction
+ └── SaveableInteractable
+ ├── Pickup
+ └── ItemSlot
+```
+
+### Class Descriptions
+
+- **InteractableBase** - Abstract base class that orchestrates the complete interaction flow using the Template Method pattern. Handles tap input, character movement, validation, and event dispatching for all interactables.
+
+- **SaveableInteractable** - Extends InteractableBase with save/load capabilities, integrating with the ManagedBehaviour save system. Provides abstract methods for JSON serialization and deserialization of state.
+
+- **OneClickInteraction** - Simplest concrete interactable that completes immediately when character arrives with no additional logic. All functionality comes from UnityEvents configured in the Inspector.
+
+- **Pickup** - Represents items that can be picked up by the follower, handling item combination and state tracking. Integrates with ItemManager and supports bilateral restoration with ItemSlots.
+
+- **ItemSlot** - Container that accepts specific items with validation for correct/incorrect/forbidden items. Manages item placement, swapping, and supports combination with special puzzle integration that allows swapping when locked.
+
+---
+
+## InteractableBase - The Template Method
+
+### Interaction Flow
+
+When a player taps an interactable, the following flow executes:
+
+```csharp
+OnTap() → CanBeClicked() → StartInteractionFlowAsync()
+ ↓
+ 1. Find Characters (player, follower)
+ 2. OnInteractionStarted() [Virtual Hook]
+ 3. Fire interactionStarted events
+ 4. MoveCharactersAsync()
+ 5. OnInteractingCharacterArrived() [Virtual Hook]
+ 6. Fire characterArrived events
+ 7. ValidateInteraction()
+ 8. DoInteraction() [Virtual Hook - OVERRIDE THIS]
+ 9. OnInteractionFinished() [Virtual Hook]
+ 10. Fire interactionComplete events
+```
+
+### Virtual Methods to Override
+
+#### 1. `CanBeClicked()` - Pre-Interaction Validation
+```csharp
+protected virtual bool CanBeClicked()
+{
+ if (!isActive) return false;
+ // Add custom checks here
+ return true;
+}
+```
+**When to override:** Add high-level validation before interaction starts (cooldowns, prerequisites, etc.)
+
+#### 2. `OnInteractionStarted()` - Setup Logic
+```csharp
+protected virtual void OnInteractionStarted()
+{
+ // Called after characters found, before movement
+ // Setup animations, sound effects, etc.
+}
+```
+**When to override:** Perform setup that needs to happen before character movement
+
+#### 3. `DoInteraction()` - Main Logic ⭐ **OVERRIDE THIS**
+```csharp
+protected override bool DoInteraction()
+{
+ // Your interaction logic here
+ return true; // Return true for success, false for failure
+}
+```
+**When to override:** **Always override this** - this is your main interaction logic
+
+#### 4. `OnInteractingCharacterArrived()` - Arrival Reaction
+```csharp
+protected virtual void OnInteractingCharacterArrived()
+{
+ // Called when character reaches interaction point
+ // Trigger arrival animations, sounds, etc.
+}
+```
+**When to override:** React to character arrival with visuals/audio
+
+#### 5. `OnInteractionFinished()` - Cleanup Logic
+```csharp
+protected virtual void OnInteractionFinished(bool success)
+{
+ // Called after interaction completes
+ // Cleanup, reset state, etc.
+}
+```
+**When to override:** Perform cleanup after interaction completes
+
+#### 6. `CanProceedWithInteraction()` - Validation
+```csharp
+protected virtual (bool canProceed, string errorMessage) CanProceedWithInteraction()
+{
+ // Validate if interaction can proceed
+ // Return error message to show to player
+ return (true, null);
+}
+```
+**When to override:** Add validation that shows error messages to player
+
+---
+
+## Creating Custom Interactables
+
+### Example 1: Simple Button (OneClickInteraction)
+
+The simplest interactable just completes when the character arrives:
+
+```csharp
+using Interactions;
+
+public class OneClickInteraction : InteractableBase
+{
+ protected override bool DoInteraction()
+ {
+ // Simply return success - no additional logic needed
+ return true;
+ }
+}
+```
+
+**Use Case:** Triggers, pressure plates, simple activators
+
+**Configuration:**
+- Set `characterToInteract` to define which character activates it
+- Use UnityEvents in inspector to trigger game logic
+
+---
+
+### Example 2: Item Pickup
+
+From `Pickup.cs` - demonstrates validation and follower interaction:
+
+```csharp
+public class Pickup : SaveableInteractable
+{
+ public PickupItemData itemData;
+ public bool IsPickedUp { get; internal set; }
+
+ protected override bool DoInteraction()
+ {
+ // Try combination first if follower is holding something
+ var heldItemObject = FollowerController?.GetHeldPickupObject();
+ var heldItemData = heldItemObject?.GetComponent()?.itemData;
+
+ var combinationResult = FollowerController.TryCombineItems(
+ this, out var resultItem
+ );
+
+ if (combinationResult == FollowerController.CombinationResult.Successful)
+ {
+ IsPickedUp = true;
+ FireCombinationEvent(resultItem, heldItemData);
+ return true;
+ }
+
+ // No combination - do regular pickup
+ FollowerController?.TryPickupItem(gameObject, itemData);
+ IsPickedUp = true;
+ OnItemPickedUp?.Invoke(itemData);
+ return true;
+ }
+}
+```
+
+**Key Patterns:**
+- Access `FollowerController` directly (set by base class)
+- Return `true` for successful pickup
+- Use custom events (`OnItemPickedUp`) for specific notifications
+
+---
+
+### Example 3: Item Slot with Validation
+
+From `ItemSlot.cs` - demonstrates complex validation and state management:
+
+```csharp
+public class ItemSlot : SaveableInteractable
+{
+ public PickupItemData itemData; // What item should go here
+ private ItemSlotState currentState = ItemSlotState.None;
+
+ protected override (bool canProceed, string errorMessage) CanProceedWithInteraction()
+ {
+ var heldItem = FollowerController?.CurrentlyHeldItemData;
+
+ // Can't interact with empty slot and no item
+ if (heldItem == null && currentlySlottedItemObject == null)
+ return (false, "This requires an item.");
+
+ // Check forbidden items
+ if (heldItem != null && currentlySlottedItemObject == null)
+ {
+ var config = interactionSettings?.GetSlotItemConfig(itemData);
+ var forbidden = config?.forbiddenItems ?? new List();
+
+ if (PickupItemData.ListContainsEquivalent(forbidden, heldItem))
+ return (false, "Can't place that here.");
+ }
+
+ return (true, null);
+ }
+
+ protected override bool DoInteraction()
+ {
+ var heldItemData = FollowerController.CurrentlyHeldItemData;
+ var heldItemObj = FollowerController.GetHeldPickupObject();
+
+ // Scenario 1: Slot empty + holding item = Slot it
+ if (heldItemData != null && currentlySlottedItemObject == null)
+ {
+ SlotItem(heldItemObj, heldItemData);
+ FollowerController.ClearHeldItem();
+ return IsSlottedItemCorrect(); // Returns true only if correct item
+ }
+
+ // Scenario 2: Slot full + holding item = Try combine or swap
+ if (currentlySlottedItemObject != null)
+ {
+ // Try combination...
+ // Or swap items...
+ }
+
+ return false;
+ }
+}
+```
+
+**Key Patterns:**
+- `CanProceedWithInteraction()` shows error messages to player
+- `DoInteraction()` returns true only for correct item (affects puzzle completion)
+- Access settings via `GameManager.GetSettingsObject()`
+
+---
+
+## Character Movement
+
+### Character Types
+
+```csharp
+public enum CharacterToInteract
+{
+ None, // No character movement
+ Trafalgar, // Player only
+ Pulver, // Follower only (player moves to range first)
+ Both // Both characters move
+}
+```
+
+Set in Inspector on `InteractableBase`.
+
+### Custom Movement Targets
+
+Add `CharacterMoveToTarget` component as child of your interactable:
+
+```csharp
+// Automatically used if present
+var moveTarget = GetComponentInChildren();
+Vector3 targetPos = moveTarget.GetTargetPosition();
+```
+
+See [Editor Reference](editor_reference.md#character-movement-targets) for details.
+
+---
+
+## Action Component System
+
+Add modular behaviors to interactables via `InteractionActionBase` components.
+
+### Creating an Action Component
+
+```csharp
+using Interactions;
+using System.Threading.Tasks;
+
+public class MyCustomAction : InteractionActionBase
+{
+ protected override async Task ExecuteAsync(
+ InteractionEventType eventType,
+ PlayerTouchController player,
+ FollowerController follower)
+ {
+ // Your action logic here
+
+ if (eventType == InteractionEventType.InteractionStarted)
+ {
+ // Play sound, spawn VFX, etc.
+ await Task.Delay(1000); // Simulate async work
+ }
+
+ return true; // Return success
+ }
+
+ protected override bool ShouldExecute(
+ InteractionEventType eventType,
+ PlayerTouchController player,
+ FollowerController follower)
+ {
+ // Add conditions for when this action should run
+ return base.ShouldExecute(eventType, player, follower);
+ }
+}
+```
+
+### Configuring in Inspector
+
+
+
+- **Respond To Events**: Select which events trigger this action
+- **Pause Interaction Flow**: If true, interaction waits for this action to complete
+
+### Built-in Action: Timeline Playback
+
+`InteractionTimelineAction` plays Unity Timeline sequences in response to events:
+
+```csharp
+// Automatically configured via Inspector
+// See Editor Reference for details
+```
+
+**Features:**
+- Character binding to timeline tracks
+- Sequential timeline playback
+- Loop options (loop all, loop last)
+- Timeout protection
+
+---
+
+## Events System
+
+### UnityEvents (Inspector-Configurable)
+
+Available on all `InteractableBase`:
+
+```csharp
+[Header("Interaction Events")]
+public UnityEvent interactionStarted;
+public UnityEvent interactionInterrupted;
+public UnityEvent characterArrived;
+public UnityEvent interactionComplete; // bool = success
+```
+
+### C# Events (Code Subscribers)
+
+Pickup example:
+```csharp
+public event Action OnItemPickedUp;
+public event Action OnItemsCombined;
+```
+
+ItemSlot example:
+```csharp
+public event Action OnItemSlotRemoved;
+public event Action OnCorrectItemSlotted;
+public event Action OnIncorrectItemSlotted;
+```
+
+### Subscribing to Events
+
+```csharp
+void Start()
+{
+ var pickup = GetComponent();
+ pickup.OnItemPickedUp += HandleItemPickedUp;
+}
+
+void HandleItemPickedUp(PickupItemData itemData)
+{
+ Debug.Log($"Picked up: {itemData.itemName}");
+}
+
+void OnDestroy()
+{
+ var pickup = GetComponent();
+ if (pickup != null)
+ pickup.OnItemPickedUp -= HandleItemPickedUp;
+}
+```
+
+---
+
+## Save/Load System Integration
+
+### Making an Interactable Saveable
+
+1. Inherit from `SaveableInteractable` instead of `InteractableBase`
+2. Define a serializable data structure
+3. Override `GetSerializableState()` and `ApplySerializableState()`
+
+### Example Implementation
+
+```csharp
+using Interactions;
+using UnityEngine;
+
+// 1. Define save data structure
+[System.Serializable]
+public class MyInteractableSaveData
+{
+ public bool hasBeenActivated;
+ public int activationCount;
+}
+
+// 2. Inherit from SaveableInteractable
+public class MyInteractable : SaveableInteractable
+{
+ private bool hasBeenActivated = false;
+ private int activationCount = 0;
+
+ // 3. Serialize state
+ protected override object GetSerializableState()
+ {
+ return new MyInteractableSaveData
+ {
+ hasBeenActivated = this.hasBeenActivated,
+ activationCount = this.activationCount
+ };
+ }
+
+ // 4. Deserialize state
+ protected override void ApplySerializableState(string serializedData)
+ {
+ var data = JsonUtility.FromJson(serializedData);
+ if (data == null) return;
+
+ this.hasBeenActivated = data.hasBeenActivated;
+ this.activationCount = data.activationCount;
+
+ // IMPORTANT: Don't fire events during restoration
+ // Don't re-run initialization logic
+ }
+
+ protected override bool DoInteraction()
+ {
+ hasBeenActivated = true;
+ activationCount++;
+ return true;
+ }
+}
+```
+---
+
+## Integration with Puzzle System
+
+Interactables can be puzzle steps by adding `ObjectiveStepBehaviour`:
+
+```csharp
+// On GameObject with Interactable component
+var stepBehaviour = gameObject.AddComponent();
+stepBehaviour.stepData = myPuzzleStepSO;
+```
+
+### Automatic Puzzle Integration
+
+`InteractableBase` automatically checks for puzzle locks:
+
+```csharp
+private (bool, string) ValidateInteractionBase()
+{
+ var step = GetComponent();
+ if (step != null && !step.IsStepUnlocked())
+ {
+ // Special case: ItemSlots can swap even when locked
+ if (!(this is ItemSlot))
+ {
+ return (false, "This step is locked!");
+ }
+ }
+ return (true, null);
+}
+```
+
+**Result:** Locked puzzle steps can't be interacted with (except ItemSlots for item swapping).
+
+---
+
+## Advanced Patterns
+
+### Async Validation
+
+For complex validation that requires async operations:
+
+```csharp
+protected override (bool canProceed, string errorMessage) CanProceedWithInteraction()
+{
+ // Synchronous validation only
+ // Async validation should be done in OnInteractionStarted
+ return (true, null);
+}
+
+protected override void OnInteractionStarted()
+{
+ // Can perform async checks here if needed
+ // But interaction flow continues automatically
+}
+```
+
+### Interrupting Interactions
+
+Interactions auto-interrupt if player cancels movement:
+
+```csharp
+// Automatically handled in MoveCharactersAsync()
+playerRef.OnMoveToCancelled += () => {
+ interactionInterrupted?.Invoke();
+ // Flow stops here
+};
+```
+
+### One-Time Interactions
+
+```csharp
+[Header("Interaction Settings")]
+public bool isOneTime = true;
+
+// Automatically disabled after first successful interaction
+// No override needed
+```
+
+### Cooldown Systems
+
+```csharp
+[Header("Interaction Settings")]
+public float cooldown = 5f; // Seconds
+
+// Automatically handled by base class
+// Interaction disabled for 5 seconds after completion
+```
\ No newline at end of file
diff --git a/docs/interactables/editor_reference.md b/docs/interactables/editor_reference.md
new file mode 100644
index 00000000..18d92a3a
--- /dev/null
+++ b/docs/interactables/editor_reference.md
@@ -0,0 +1,305 @@
+# Interactables System - Editor Reference
+
+## Table of Contents
+
+1. [Overview](#overview)
+2. [Adding Interactables to Scene](#adding-interactables-to-scene)
+3. [InteractableBase Inspector](#interactablebase-inspector)
+ - [Interaction Settings](#interaction-settings)
+ - [Interaction Events](#interaction-events-unityevents)
+4. [Character Movement Targets](#character-movement-targets)
+5. [Pickup Inspector](#pickup-inspector)
+6. [ItemSlot Inspector](#itemslot-inspector)
+7. [OneClickInteraction Inspector](#oneclickinteraction-inspector)
+8. [Interaction Action Components](#interaction-action-components)
+9. [Custom Action Components](#custom-action-components)
+10. [Puzzle Integration](#puzzle-integration)
+11. [Save System Configuration](#save-system-configuration)
+
+---
+
+## Overview
+
+This guide covers configuring interactables using the Unity Inspector and scene tools. It might be helpful, although
+ not necessary to be familiar with the code architecture covered in the [Code Reference](code_reference.md).
+
+---
+
+## Adding Interactables to Scene
+
+### Method 1: Add Component Manually
+Select GameObject → Add Component → Search "Interactable" → Choose type
+
+### Method 2: Use Interactable Editor
+`AppleHills > Interactable Editor` → Scene tab → Select GameObject → Click button for desired type
+
+See [Editor Tools Reference](editor_tools_reference.md#interactable-editor) for details.
+
+---
+
+## InteractableBase Inspector
+
+
+
+### Interaction Settings
+
+**Is One Time** - Disable after first successful interaction (switches, consumables)
+
+**Cooldown** - Temporarily disable after use, in seconds. `-1` = no cooldown (levers, buttons)
+
+**Character To Interact** - Which character(s) move to activate:
+- **None** - No movement, instant interaction
+- **Trafalgar** - Player moves to point
+- **Pulver** - Follower moves (player moves to range first)
+- **Both** - Both characters move
+
+### Interaction Events (UnityEvents)
+
+
+
+**Interaction Started** `` - Fires after tap, before movement
+
+**Interaction Interrupted** - Player cancels or validation fails
+
+**Character Arrived** - Character reaches destination
+
+**Interaction Complete** `` - After DoInteraction(), bool = success
+
+### Example Event Configuration
+
+
+
+**Door that opens when player arrives:**
+- Character To Interact: `Trafalgar`
+- Character Arrived: `DoorAnimator.SetTrigger("Open")`, `AudioSource.Play()`
+- Interaction Complete: `PuzzleStep.CompleteStep()` (if success)
+
+---
+
+## Character Movement Targets
+
+### Default Movement
+
+Without `CharacterMoveToTarget`, characters move to default distances configured in `GameManager`:
+- `PlayerStopDistance` - Follower interactions (~1.5 units)
+- `PlayerStopDistanceDirectInteraction` - Player interactions (~0.5 units)
+
+### Custom Movement Targets
+
+Add `CharacterMoveToTarget` component to child GameObject:
+
+
+
+**Fields:**
+- **Character Type** - Which character (Trafalgar/Pulver/Both/None)
+- **Position Offset** - Offset from transform position
+
+### Scene Gizmos
+
+
+
+**Colors:** 🔵 Blue (Trafalgar), 🟠 Orange (Pulver), 🟣 Purple (Both), ⚪ Gray (None)
+
+---
+
+## Pickup Inspector
+
+
+
+**Required Fields:**
+
+**Item Data** - `PickupItemData` ScriptableObject defining the item. Create via `Assets > Create > AppleHills > Items + Puzzles > Pickup Item Data`
+
+**Icon Renderer** - `SpriteRenderer` displaying item icon (auto-assigned if not set)
+
+### PickupItemData ScriptableObject
+
+
+
+**Fields:** Item Name, Description, Map Sprite, Pick Up Sound, Drop Sound
+
+**Item ID** (Read-Only) - Auto-generated unique identifier for save/load
+
+---
+
+## ItemSlot Inspector
+
+
+
+**Required Fields:**
+
+**Item Data** - `PickupItemData` defining the **correct** item for this slot
+
+**Icon Renderer** - `SpriteRenderer` showing slot icon (background/outline)
+
+**Slotted Item Renderer** - `SpriteRenderer` showing currently slotted item (usually child GameObject)
+
+### Slot Events
+
+
+
+**On Item Slotted** - Any item placed
+**On Item Slot Removed** - Item removed
+**On Correct Item Slotted** - Correct item placed (also fires `interactionComplete(true)`)
+**On Incorrect Item Slotted** - Wrong item placed
+**On Forbidden Item Slotted** - Forbidden item attempted
+
+
+### Slot Item Configuration (Settings)
+
+
+
+Configured in `InteractionSettings` at `Assets/Settings/InteractionSettings`:
+- **Correct Items** - List of accepted items
+- **Forbidden Items** - Items that can't be placed
+- **Incorrect Items** - Items that slot but aren't correct
+
+
+---
+
+## OneClickInteraction Inspector
+
+
+
+**No additional fields** - only inherits `InteractableBase` settings.
+
+### Typical Configuration
+
+- **Character To Interact:** `Trafalgar` or `Pulver`
+- **Is One Time:** Depends on use case
+- **Interaction Complete Event:** Configure to trigger game logic
+
+### Example Use Cases
+
+**Pressure Plate:**
+- Character To Interact: `Pulver`
+- Is One Time: `false`
+- Interaction Complete: Call `Door.Open()`
+
+**Tutorial Trigger:**
+- Character To Interact: `None`
+- Is One Time: `true`
+- Interaction Started: Call `TutorialManager.ShowTip()`
+
+**Dialogue Starter:**
+- Character To Interact: `Both`
+- Is One Time: `false`
+- Character Arrived: Call `DialogueManager.StartDialogue()`
+
+---
+
+## Interaction Action Components
+
+### InteractionTimelineAction
+
+
+
+Plays Unity Timeline sequences in response to interaction events.
+
+#### Required Fields
+
+**Playable Director**
+- **Type:** `PlayableDirector` component
+- **Purpose:** Timeline player
+- **Setup:** Auto-assigned from same GameObject if present
+
+**Timeline Mappings** (Array)
+Each element maps an interaction event to timeline(s):
+
+
+
+##### Event Type
+- **Type:** `InteractionEventType` enum
+- **Options:**
+ - `InteractionStarted`
+ - `PlayerArrived`
+ - `InteractingCharacterArrived`
+ - `InteractionComplete`
+ - `InteractionInterrupted`
+- **Purpose:** When to play this timeline
+
+##### Timelines (Array)
+- **Type:** `PlayableAsset[]`
+- **Purpose:** Timeline(s) to play for this event
+- **Note:** Plays sequentially if multiple
+
+##### Bind Player Character
+- **Type:** `bool`
+- **Purpose:** Automatically bind player to timeline track
+- **Track Name:** `Player` (customizable via Player Track Name field)
+
+##### Bind Pulver Character
+- **Type:** `bool`
+- **Purpose:** Automatically bind follower to timeline track
+- **Track Name:** `Pulver` (customizable via Pulver Track Name field)
+
+##### Player Track Name / Pulver Track Name
+- **Type:** `string`
+- **Default:** `"Player"` / `"Pulver"`
+- **Purpose:** Name of timeline track to bind character to
+- **Note:** Must match track name in Timeline asset exactly
+
+##### Timeout Seconds
+- **Type:** `float`
+- **Default:** `30`
+- **Purpose:** Safety timeout - auto-complete if timeline doesn't finish
+- **Use Case:** Prevent stuck interactions if timeline errors
+
+##### Loop Last / Loop All
+- **Type:** `bool`
+- **Purpose:** Loop behavior for timeline sequence
+- **Loop Last:** Replays final timeline on next interaction
+- **Loop All:** Cycles through all timelines repeatedly
+
+---
+
+## Custom Action Components
+
+See [Code Reference - Action Component System](code_reference.md#action-component-system).
+
+**Base Fields:**
+- **Respond To Events** - Which events trigger this action
+- **Pause Interaction Flow** - Wait for completion (`true`) or run in background (`false`)
+
+---
+
+## Puzzle Integration
+
+### Adding Puzzle Step to Interactable
+
+1. Select interactable GameObject
+2. Add Component → `ObjectiveStepBehaviour`
+3. Assign `Step Data` (PuzzleStepSO asset)
+
+
+
+**GameObject has two components:**
+- **ItemSlot** (or other Interactable type)
+- **ObjectiveStepBehaviour**
+
+**Behavior:**
+- Interactable locked until puzzle step unlocked
+- Successful interaction (return `true` from `DoInteraction()`) completes puzzle step
+- ItemSlots can still swap items when locked (special case)
+
+### Automatic Step Completion
+
+**For Pickup:**
+```csharp
+protected override bool DoInteraction()
+{
+ // ...pickup logic...
+ return true; // Automatically completes puzzle step if present
+}
+```
+
+**For ItemSlot:**
+```csharp
+protected override bool DoInteraction()
+{
+ // ...slot logic...
+ return IsSlottedItemCorrect(); // Only completes if correct item
+}
+```
+
+No additional code needed - `InteractableBase` handles step completion automatically.
\ No newline at end of file
diff --git a/docs/interactables/editor_tools_reference.md b/docs/interactables/editor_tools_reference.md
new file mode 100644
index 00000000..a43214e1
--- /dev/null
+++ b/docs/interactables/editor_tools_reference.md
@@ -0,0 +1,251 @@
+# Editor Tools Reference
+
+## Overview
+
+AppleHills provides two specialized editor tools for managing puzzles and interactables:
+- **Interactable Editor** - Manage scene interactables and debug interactions
+- **Puzzle Editor** - Manage puzzle steps and debug puzzle flow
+
+Both tools are accessible via the `AppleHills` menu and follow a consistent two-tab design pattern for editing and debugging.
+
+---
+
+## Interactable Editor
+
+**Menu:** `AppleHills > Interactable Editor`
+
+The Interactable Editor provides scene-based management and runtime debugging for interactable objects in your scenes.
+
+### Edit Tab
+
+
+
+The Edit tab lets you browse, select, and modify all interactables in the current scene with a real-time inspector.
+
+#### Left Panel - Scene Interactable List
+
+**Refresh Button** - Manual refresh (auto-refreshes on scene changes, hierarchy changes, and play mode toggle)
+
+**Found Count** - Displays total number of interactables found in the active scene
+
+**Search Field** - Filter interactables by GameObject name for quick access
+
+**Interactable Cards** - Each card shows:
+- GameObject name
+- Interactable component type
+- Item/Slot data reference (for Pickup and ItemSlot types)
+- Ping button to highlight in scene hierarchy
+
+**Auto-Discovery:** Automatically scans for all `InteractableBase` components when:
+- Scene loads or unloads
+- GameObjects or components are added/removed
+- Play mode is toggled
+
+#### Right Panel - Dynamic Inspector
+
+The right panel adapts based on what's selected:
+
+**Nothing Selected State:**
+- Shows a help message prompting you to select an interactable from the list
+
+**GameObject Without Interactable State:**
+- Displays "Add Interactable Component" buttons for:
+ - OneClickInteraction
+ - Pickup
+ - ItemSlot
+ - SaveableInteractable
+ - InteractableBase
+- Clicking any button adds the component with full undo support and auto-refreshes the list
+
+**Interactable Selected State:**
+- Shows full Unity inspector for the selected component
+- All changes auto-save with undo/redo support (Ctrl+Z / Ctrl+Y)
+- Additional Information section displays:
+ - Component type
+ - Attached action components with Ping buttons
+ - Type-specific data (Item Data for Pickup, Slot Data for ItemSlot)
+ - In Play Mode: shows slotted object for ItemSlot types
+
+**Selection Synchronization:**
+- Bidirectional sync between editor list and scene hierarchy
+- Selecting in the list highlights in hierarchy and vice versa
+- Selecting a GameObject without an interactable shows the "Add Component" interface
+
+---
+
+
+
+### Debug Tab
+
+**Availability:** Play Mode only
+
+The Debug tab provides runtime testing tools for triggering interactions and events on interactables.
+
+#### Interactable Debug Cards
+
+Each debug card represents one interactable in the scene and includes:
+
+**Header Section:**
+- GameObject name (bold text)
+- Interactable component type (gray text)
+- Ping button (locates object in hierarchy)
+
+**Full Interaction Button:**
+- Simulates complete `OnTap()` flow
+- Triggers character movement and full event chain
+- Tests end-to-end interaction behavior
+
+**Individual Event Triggers:**
+- **Started** - Calls `OnInteractionStarted()` and fires `interactionStarted` event
+- **Arrived** - Calls `OnInteractingCharacterArrived()` and fires `characterArrived` event
+- **Do Interaction** - Calls `DoInteraction()` directly to test core interaction logic
+- **Complete (Success)** - Calls `OnInteractionFinished(true)` and triggers puzzle completion
+- **Complete (Fail)** - Calls `OnInteractionFinished(false)` to test failure handling
+- **Interrupted** - Invokes `interactionInterrupted` event
+
+**Registered Actions Display:**
+- Lists all action components registered to this interactable
+- Shows which events each action responds to
+
+#### Common Testing Workflows
+
+**Test Full Interaction:**
+1. Enter Play Mode
+2. Find target interactable in debug list
+3. Click **Full Interaction** button
+4. Verify complete behavior chain
+
+**Test Specific Event:**
+1. Enter Play Mode
+2. Locate interactable
+3. Click individual event button (e.g., **Started** or **Arrived**)
+4. Verify specific event behavior
+
+**Test Event Sequence:**
+1. Click **Started**
+2. Click **Arrived**
+3. Click **Do Interaction**
+4. Click **Complete (Success)**
+5. Verify full event chain executes correctly
+
+**Test Action Integration:**
+1. Find interactable with timeline or dialogue action
+2. Check Registered Actions to confirm action is attached
+3. Click **Started** or appropriate event trigger
+4. Verify action executes (timeline plays, dialogue shows, etc.)
+
+**Test Puzzle Integration:**
+1. Open both Interactable Editor and Puzzle Editor
+2. Verify required puzzle step is unlocked in Puzzle Editor
+3. Click **Full Interaction** in Interactable Editor
+4. Switch to Puzzle Editor and verify step marked as completed
+
+---
+
+## Puzzle Editor
+
+**Menu:** `AppleHills > Puzzle Editor`
+
+
+
+The Puzzle Editor manages puzzle step assets and provides runtime debugging for the puzzle progression system.
+
+### Edit Tab
+
+The Edit tab displays all `PuzzleStepSO` assets in your project with full editing capabilities.
+
+#### Left Panel - Puzzle Step List
+
+**Search Field** - Filter puzzle steps by name
+
+**Folder Organization:**
+- Steps are grouped by their asset folder location
+- Click folder headers to expand/collapse groups
+- Helps organize large numbers of puzzle steps
+
+**Step Cards** - Each card displays:
+- Display name (user-friendly identifier)
+- Step ID (unique technical identifier)
+- Dependency information (unlocked by / unlocks)
+
+**Toolbar Actions:**
+- **Refresh** - Reloads all puzzle step assets from project
+- **Create New** - Opens creation dialog
+
+**Creating New Steps:**
+1. Click **Create New** button
+2. Enter step name (stepId auto-generates from name)
+3. Select destination folder
+4. Click Create
+5. New step appears in list and is auto-selected
+
+#### Right Panel - Step Inspector
+
+When a puzzle step is selected, the inspector shows:
+
+**Basic Properties:**
+- **Display Name** - Editable user-friendly name for the step
+- **Step ID** - Read-only unique identifier (lowercase, underscored format)
+
+**Dependencies Configuration:**
+- **Unlocked By** - List of steps that must complete before this step unlocks
+ - Drag and drop `PuzzleStepSO` assets to add dependencies
+ - Empty list means this is an initial step (unlocked by default)
+- **Unlocks** - List of steps that this step will unlock when completed
+ - Bidirectional relationship (automatically syncs with "Unlocked By" on other steps)
+ - Edit from either side of the relationship
+
+**Asset Management:**
+- **Asset Path** - Shows full file path to the .asset file
+- **Delete Button** - Permanently deletes the step asset
+ - Shows confirmation dialog before deletion
+ - Cannot be undone after confirmation
+
+**Auto-Save:** All changes save automatically to the asset with full undo/redo support (Ctrl+Z / Ctrl+Y)
+
+
+
+---
+
+### Debug Tab
+
+**Availability:** Play Mode only
+
+The Debug tab provides runtime testing and debugging tools for the puzzle progression system.
+
+#### Toolbar
+
+**Current Level Display:**
+- Shows the name of the currently loaded puzzle level
+- Updates automatically when scenes change
+- Displays "No level loaded" if puzzle system is inactive
+
+**Unlock All Button:**
+- Unlocks and completes all puzzle steps in the current level
+- Processes steps in dependency order using iterative algorithm
+- Logs progression to console for debugging
+- Useful for testing late-game content or verifying completion flow
+
+#### Step List
+
+Each step in the current level displays:
+
+**Step Header:**
+- Display name in bold text
+- Step ID in gray text below name
+
+**State Indicators:**
+- 🔒 **Locked** (gray background) - Dependencies not met, step unavailable
+- 🔓 **Unlocked** (yellow background) - Available for interaction but not completed
+- ✅ **Completed** (green background) - Successfully completed
+
+**Action Buttons:**
+- **Toggle Lock** - Manually lock/unlock the step
+ - Bypasses normal dependency requirements
+ - Useful for testing specific scenarios
+ - Does not affect dependent steps automatically
+- **Complete** - Marks step as completed
+ - Only enabled when step is unlocked
+ - Fires completion events
+ - Automatically unlocks dependent steps
+ - Updates state indicators in real-time
diff --git a/docs/interactables_readme.md b/docs/interactables_readme.md
deleted file mode 100644
index 06900bbc..00000000
--- a/docs/interactables_readme.md
+++ /dev/null
@@ -1,253 +0,0 @@
-# Apple Hills Interaction System
-
-A concise, code-first guide to creating and extending interactions using `Interactable` and modular action/requirement components. Designed to match the style of the other updated docs (TOC, inline code, case studies).
-
-## Table of Contents
-- [What This Solves](#what-this-solves)
-- [Architecture at a Glance](#architecture-at-a-glance)
-- [Quick Start (Code-First)](#quick-start-code-first)
- - [Subscribe to Interaction Events](#subscribe-to-interaction-events)
- - [Create a Custom Action](#create-a-custom-action)
- - [Trigger Programmatically](#trigger-programmatically)
-- [Core Components](#core-components)
- - [`Interactable`](#interactable)
- - [`CharacterMoveToTarget`](#charactermovetotarget)
- - [`InteractionActionBase` and concrete actions](#interactionactionbase-and-concrete-actions)
- - [`InteractionRequirementBase`](#interactionrequirementbase)
-- [Interaction Event Flow](#interaction-event-flow)
-- [Case Studies](#case-studies)
- - [Open a Door on Arrival](#open-a-door-on-arrival)
- - [Pick Up an Item then Play Timeline](#pick-up-an-item-then-play-timeline)
- - [Kick Off Dialogue When Player Arrives](#kick-off-dialogue-when-player-arrives)
-- [Troubleshooting / FAQ](#troubleshooting--faq)
-- [Paths & Namespaces](#paths--namespaces)
-- [Change Log](#change-log)
-
-## What This Solves
-- Standardized interaction lifecycle with reliable events (`InteractionStarted`, `PlayerArrived`, `InteractingCharacterArrived`, `InteractionComplete`, `InteractionInterrupted`).
-- Composable behavior via components derived from `InteractionActionBase` and `InteractionRequirementBase`.
-- Clean separation of input, locomotion-to-target, cinematic timelines, and game logic.
-
-## Architecture at a Glance
-- Driver: `Interactable` — owns lifecycle, input hook, character selection via `CharacterToInteract`, one‑shot/cooldown, and event dispatch.
-- Targets: `CharacterMoveToTarget` — editor-authored world points for `Trafalgar`/`Pulver` to path to before executing actions.
-- Actions: `InteractionActionBase` (abstract) — modular responses to specific `InteractionEventType` values; can pause the flow with async tasks.
-- Requirements: `InteractionRequirementBase` (abstract) — gatekeepers for availability; multiple can be attached.
-- Cinematics: `InteractionTimelineAction` — plays one or more `PlayableAsset` timelines per event; optional character auto-binding.
-
-## Quick Start (Code-First)
-
-### Subscribe to Interaction Events
-```csharp
-using Interactions;
-using UnityEngine;
-
-public class InteractDebugHooks : MonoBehaviour
-{
- [SerializeField] private Interactable interactable;
-
- private void OnEnable()
- {
- interactable.interactionStarted.AddListener(OnStarted);
- interactable.characterArrived.AddListener(OnCharacterArrived);
- interactable.interactionInterrupted.AddListener(OnInterrupted);
- interactable.interactionComplete.AddListener(OnComplete);
- }
-
- private void OnDisable()
- {
- interactable.interactionStarted.RemoveListener(OnStarted);
- interactable.characterArrived.RemoveListener(OnCharacterArrived);
- interactable.interactionInterrupted.RemoveListener(OnInterrupted);
- interactable.interactionComplete.RemoveListener(OnComplete);
- }
-
- private void OnStarted(Input.PlayerTouchController player, FollowerController follower)
- => Debug.Log("Interaction started");
-
- private void OnCharacterArrived() => Debug.Log("Character arrived");
- private void OnInterrupted() => Debug.Log("Interaction interrupted");
- private void OnComplete(bool success) => Debug.Log($"Interaction complete: {success}");
-}
-```
-
-### Create a Custom Action
-```csharp
-using System.Threading.Tasks;
-using Interactions;
-using Input;
-using UnityEngine;
-
-public class PlaySfxOnArrivalAction : InteractionActionBase
-{
- [SerializeField] private AudioSource sfx;
-
- private void Reset()
- {
- // React to the arrival event; don't block the flow
- respondToEvents = new() { InteractionEventType.InteractingCharacterArrived };
- pauseInteractionFlow = false;
- }
-
- protected override bool ShouldExecute(InteractionEventType evt, PlayerTouchController player, FollowerController follower)
- {
- return sfx != null;
- }
-
- protected override async Task ExecuteAsync(InteractionEventType evt, PlayerTouchController player, FollowerController follower)
- {
- sfx.Play();
- // non-blocking action returns immediately when pauseInteractionFlow == false
- return true;
- }
-}
-```
-Attach this component under the same hierarchy as an `Interactable`. Registration is automatic via `OnEnable()`/`OnDisable()` in `InteractionActionBase`.
-
-### Trigger Programmatically
-Normally input goes through `ITouchInputConsumer.OnTap(...)`. For testing, you can call the public tap handler:
-```csharp
-using UnityEngine;
-using Interactions;
-
-public class TestTrigger : MonoBehaviour
-{
- [SerializeField] private Interactable interactable;
-
- [ContextMenu("Trigger Interact (dev)")]
- private void Trigger()
- {
- interactable.OnTap(interactable.transform.position);
- }
-}
-```
-
-## Core Components
-
-### `Interactable`
-- Handles input, cooldowns (`cooldown`), one‑shot (`isOneTime`), and which character participates (`characterToInteract`).
-- Exposes events: `interactionStarted`, `characterArrived`, `interactionInterrupted`, `interactionComplete`.
-- Discovers and dispatches to child `InteractionActionBase` components; awaits those that request to pause.
-
-
-
-### `CharacterMoveToTarget`
-Defines the world positions characters should reach before actions evaluate.
-- Can target `Trafalgar`, `Pulver`, or `Both` via configuration.
-- Supports offsets and editor gizmos; multiple instances allowed.
-
-
-
-### `InteractionActionBase` and concrete actions
-- Filter by `InteractionEventType` using `respondToEvents`.
-- Control flow with `pauseInteractionFlow` and async `ExecuteAsync(...)`.
-- Built‑in example: `InteractionTimelineAction` for cinematics.
-
-
-
-### `InteractionRequirementBase`
-- Attach one or more to gate the interaction based on items, puzzles, proximity, etc.
-
-## Interaction Event Flow
-1. `InteractionStarted`
-2. `PlayerArrived`
-3. `InteractingCharacterArrived`
-4. `InteractionComplete` (bool success)
-5. `InteractionInterrupted`
-
-Actions receive these events in order and may run concurrently; those with `pauseInteractionFlow` true are awaited.
-
-## Case Studies
-
-### Open a Door on Arrival
-```csharp
-using System.Threading.Tasks;
-using Interactions;
-using Input;
-using UnityEngine;
-
-public class DoorOpenOnArrival : InteractionActionBase
-{
- [SerializeField] private Animator animator; // expects a bool parameter "Open"
-
- private void Reset()
- {
- respondToEvents = new() { InteractionEventType.InteractingCharacterArrived };
- pauseInteractionFlow = false;
- }
-
- protected override async Task ExecuteAsync(InteractionEventType evt, PlayerTouchController p, FollowerController f)
- {
- animator.SetBool("Open", true);
- return true;
- }
-}
-```
-
-### Pick Up an Item then Play Timeline
-Attach two actions: your `PickupItemAction` that pauses until the item is collected, and an `InteractionTimelineAction` mapped to `InteractionEventType.InteractionComplete` to celebrate.
-
-### Kick Off Dialogue When Player Arrives
-```csharp
-using System.Threading.Tasks;
-using Dialogue;
-using Input;
-using Interactions;
-using UnityEngine;
-
-public class StartDialogueOnArrival : InteractionActionBase
-{
- [SerializeField] private DialogueComponent dialogue;
-
- private void Reset()
- {
- respondToEvents = new() { InteractionEventType.PlayerArrived };
- pauseInteractionFlow = false;
- }
-
- protected override async Task ExecuteAsync(InteractionEventType evt, PlayerTouchController p, FollowerController f)
- {
- dialogue.StartDialogue();
- return true;
- }
-}
-```
-
-## Troubleshooting / FAQ
-- Interaction doesn’t fire:
- - Confirm `Interactable` is active and not in cooldown or already completed (`isOneTime`).
- - Ensure `CharacterMoveToTarget` exists for the selected `CharacterToInteract`.
-- Actions not running:
- - Verify `respondToEvents` includes the lifecycle moment you expect.
- - Check that the component sits under the same hierarchy so it registers with the `Interactable`.
-- Timeline never finishes:
- - Make sure `InteractionTimelineAction` has valid `PlayableAsset` entries and binding flags.
-- Double triggers:
- - Guard reentry in your actions or check `_interactionInProgress` usage in `Interactable` by following logs.
-
-## Paths & Namespaces
-- Scripts: `Assets/Scripts/Interactions/`
- - `Interactable.cs`
- - `InteractionActionBase.cs`
- - `InteractionTimelineAction.cs`
- - `InteractionEventType.cs`
- - `InteractionRequirementBase.cs`
-- Editor tooling: `Assets/Editor/InteractableEditor.cs`
-- Primary namespace: `Interactions`
-
-## Additional Editor Visuals
-- Timeline mapping configuration UI:
-
-
-
-- Unity Timeline editor when authoring cinematics for interactions:
-
-
-
-- Example target placement in Scene view:
-
-
-
-## Change Log
-- v1.1: Added Table of Contents, code-first snippets, case studies, standardized inline code references, preserved existing editor images, and added troubleshooting/paths.
-- v1.0: Original overview and setup guide.
diff --git a/docs/media/character_move_target_setup.png b/docs/media/character_move_target_setup.png
new file mode 100644
index 00000000..909d5b1b
Binary files /dev/null and b/docs/media/character_move_target_setup.png differ
diff --git a/docs/media/interactable_base_inspector.png b/docs/media/interactable_base_inspector.png
new file mode 100644
index 00000000..58b63813
Binary files /dev/null and b/docs/media/interactable_base_inspector.png differ
diff --git a/docs/media/interactable_editor_debug.png b/docs/media/interactable_editor_debug.png
new file mode 100644
index 00000000..678d3eda
Binary files /dev/null and b/docs/media/interactable_editor_debug.png differ
diff --git a/docs/media/interactable_editor_edit.png b/docs/media/interactable_editor_edit.png
new file mode 100644
index 00000000..fa7a3d20
Binary files /dev/null and b/docs/media/interactable_editor_edit.png differ
diff --git a/docs/media/interactable_event_configuration_example.png b/docs/media/interactable_event_configuration_example.png
new file mode 100644
index 00000000..f81cabf5
Binary files /dev/null and b/docs/media/interactable_event_configuration_example.png differ
diff --git a/docs/media/interactable_events_inspector.png b/docs/media/interactable_events_inspector.png
new file mode 100644
index 00000000..b35c6bb3
Binary files /dev/null and b/docs/media/interactable_events_inspector.png differ
diff --git a/docs/media/item_slot_events.png b/docs/media/item_slot_events.png
new file mode 100644
index 00000000..c8abe95b
Binary files /dev/null and b/docs/media/item_slot_events.png differ
diff --git a/docs/media/item_slot_inspector.png b/docs/media/item_slot_inspector.png
new file mode 100644
index 00000000..a68254cf
Binary files /dev/null and b/docs/media/item_slot_inspector.png differ
diff --git a/docs/media/movement_target_gizmos.png b/docs/media/movement_target_gizmos.png
new file mode 100644
index 00000000..ec73ba20
Binary files /dev/null and b/docs/media/movement_target_gizmos.png differ
diff --git a/docs/media/oneclick_inspector.png b/docs/media/oneclick_inspector.png
new file mode 100644
index 00000000..e177d3aa
Binary files /dev/null and b/docs/media/oneclick_inspector.png differ
diff --git a/docs/media/pickup_inspector.png b/docs/media/pickup_inspector.png
new file mode 100644
index 00000000..6cabf5cf
Binary files /dev/null and b/docs/media/pickup_inspector.png differ
diff --git a/docs/media/pickup_item_data_inspector.png b/docs/media/pickup_item_data_inspector.png
new file mode 100644
index 00000000..be08ba06
Binary files /dev/null and b/docs/media/pickup_item_data_inspector.png differ
diff --git a/docs/media/puzzle_editor_debug.png b/docs/media/puzzle_editor_debug.png
new file mode 100644
index 00000000..04e5856d
Binary files /dev/null and b/docs/media/puzzle_editor_debug.png differ
diff --git a/docs/media/puzzle_editor_edit.png b/docs/media/puzzle_editor_edit.png
new file mode 100644
index 00000000..f698c870
Binary files /dev/null and b/docs/media/puzzle_editor_edit.png differ
diff --git a/docs/media/puzzle_step_integration.png b/docs/media/puzzle_step_integration.png
new file mode 100644
index 00000000..2d7ba952
Binary files /dev/null and b/docs/media/puzzle_step_integration.png differ
diff --git a/docs/media/slot_item_config_settings.png b/docs/media/slot_item_config_settings.png
new file mode 100644
index 00000000..b16ebe20
Binary files /dev/null and b/docs/media/slot_item_config_settings.png differ
diff --git a/docs/media/timeline_mapping_element.png b/docs/media/timeline_mapping_element.png
new file mode 100644
index 00000000..1b3e0cd7
Binary files /dev/null and b/docs/media/timeline_mapping_element.png differ