diff --git a/Assets/_DDD/_Scripts/RestaurantCharacter/AI/Common/Actions/LookAtInteractionTarget.cs b/Assets/_DDD/_Scripts/RestaurantCharacter/AI/Common/Actions/LookAtInteractionTarget.cs
new file mode 100644
index 000000000..8abdaabe7
--- /dev/null
+++ b/Assets/_DDD/_Scripts/RestaurantCharacter/AI/Common/Actions/LookAtInteractionTarget.cs
@@ -0,0 +1,140 @@
+using Opsive.BehaviorDesigner.Runtime.Tasks;
+using Opsive.BehaviorDesigner.Runtime.Tasks.Actions;
+using UnityEngine;
+
+namespace DDD
+{
+ ///
+ /// 인터랙션 타겟을 바라보도록 시각(Spine/애니메이션) 컴포넌트에 위임하는 액션의 껍데기/골격.
+ /// 실제 회전/스파인 제어 로직은 별도의 Visual 컴포넌트(예: Spine/Animation Controller)에서 구현하십시오.
+ ///
+ public class LookAtInteractionTarget : Action
+ {
+ [Header("Target Settings")]
+ [Tooltip("InteractionPoints를 사용해 가장 적절한 지점을 바라봄")]
+ [SerializeField] private bool useInteractionPoints = true;
+ [Tooltip("타겟이 없을 때 즉시 실패할지 여부")]
+ [SerializeField] private bool failIfNoTarget = true;
+
+ [Header("Update Settings")]
+ [Tooltip("프레임마다 갱신하여 지속적으로 바라볼지 (Running 반환) 여부. 비활성화 시 1회만 시도하고 성공 처리")]
+ [SerializeField] private bool continuousUpdate = true;
+
+ // Visual 전용 컴포넌트(나중 구현)를 위한 최소 인터페이스
+ // 실제 구현은 Spine/애니메이션 제어 컴포넌트에서 이 인터페이스를 구현하세요.
+ public interface ILookAtVisual
+ {
+ // 초기 시작 시도. 성공 여부를 반환할 수 있으나, 본 액션은 성공/실패에 민감하지 않습니다.
+ bool TryBeginLookAt(Vector3 worldPosition);
+ // 매 프레임 갱신 시 호출됩니다.
+ void UpdateLookAt(Vector3 worldPosition);
+ // 액션 종료 시 호출됩니다.
+ void EndLookAt();
+ }
+
+ private ILookAtVisual visual;
+ private GameObject cachedTarget;
+ private bool isLooking;
+ private Vector3 currentLookPosition;
+
+ public override void OnStart()
+ {
+ visual = gameObject.GetComponentInParent();
+ cachedTarget = null;
+ isLooking = false;
+ }
+
+ public override TaskStatus OnUpdate()
+ {
+ var target = GetTarget();
+ if (target == null)
+ {
+ if (isLooking)
+ {
+ // 타겟이 사라졌다면 정리
+ visual?.EndLookAt();
+ isLooking = false;
+ }
+ return failIfNoTarget ? TaskStatus.Failure : TaskStatus.Success;
+ }
+
+ currentLookPosition = CalculateLookPosition(target);
+
+ if (!isLooking)
+ {
+ visual?.TryBeginLookAt(currentLookPosition);
+ isLooking = true;
+ }
+ else
+ {
+ visual?.UpdateLookAt(currentLookPosition);
+ }
+
+ // 연속 업데이트면 Running, 아니면 1회만 시도 후 Success 반환
+ return continuousUpdate ? TaskStatus.Running : TaskStatus.Success;
+ }
+
+ public override void OnEnd()
+ {
+ if (isLooking)
+ {
+ visual?.EndLookAt();
+ isLooking = false;
+ }
+ cachedTarget = null;
+ }
+
+ private GameObject GetTarget()
+ {
+ // 캐시된 타겟이 유효하면 재사용
+ if (IsValidTarget(cachedTarget))
+ return cachedTarget;
+
+ // 블랙보드에서 타겟 검색
+ cachedTarget = gameObject.GetComponentInParent()
+ ?.GetCurrentInteractionTarget();
+
+ if (IsValidTarget(cachedTarget))
+ return cachedTarget;
+
+ // Interactor의 포커스된 타겟 검색
+ var interactor = gameObject.GetComponentInParent();
+ var focusedInteractable = interactor?.GetFocusedInteractable();
+ cachedTarget = focusedInteractable?.GetInteractableGameObject();
+
+ return cachedTarget;
+ }
+
+ private static bool IsValidTarget(GameObject target) => target != null && target;
+
+ private Vector3 CalculateLookPosition(GameObject target)
+ {
+ if (!useInteractionPoints)
+ return target.transform.position;
+
+ if (target.TryGetComponent(out var ric))
+ {
+ var points = ric.GetInteractionPoints();
+ if (points == null || points.Length == 0)
+ return target.transform.position;
+
+ // 가장 가까운 상호작용 지점 선택 (MoveTo와 동일한 기준)
+ var agentPos = transform.position;
+ var nearest = target.transform.position;
+ var minSqr = float.MaxValue;
+ foreach (var p in points)
+ {
+ var d = (p - agentPos).sqrMagnitude;
+ if (d < minSqr)
+ {
+ minSqr = d;
+ nearest = p;
+ }
+ }
+ return nearest;
+ }
+
+ return target.transform.position;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Assets/_DDD/_Scripts/RestaurantCharacter/AI/Common/Actions/LookAtInteractionTarget.cs.meta b/Assets/_DDD/_Scripts/RestaurantCharacter/AI/Common/Actions/LookAtInteractionTarget.cs.meta
new file mode 100644
index 000000000..0f840f1cf
--- /dev/null
+++ b/Assets/_DDD/_Scripts/RestaurantCharacter/AI/Common/Actions/LookAtInteractionTarget.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: a973b52fbfe64f8981b2a1d33864d2eb
+timeCreated: 1755771294
\ No newline at end of file
diff --git a/Assets/_DDD/_Scripts/RestaurantCharacter/AI/Common/Actions/MoveToInteractionTarget.cs b/Assets/_DDD/_Scripts/RestaurantCharacter/AI/Common/Actions/MoveToInteractionTarget.cs
index b43e70e98..192081afc 100644
--- a/Assets/_DDD/_Scripts/RestaurantCharacter/AI/Common/Actions/MoveToInteractionTarget.cs
+++ b/Assets/_DDD/_Scripts/RestaurantCharacter/AI/Common/Actions/MoveToInteractionTarget.cs
@@ -17,7 +17,7 @@ public class MoveToInteractionTarget : Action
[Header("Movement Settings")]
[Tooltip("목적지 도달 거리")]
- [SerializeField] private float stoppingDistance = 0.5f;
+ [SerializeField] private float stoppingDistance = 0.01f;
[Tooltip("목적지 재계산 주기(초), 0 이하면 비활성화")]
[SerializeField] private float repathInterval = 0.5f;