Files
GhiVideo_v4/docs/history/2026-07-02_POI라벨사라짐-팝업재등장-수정.md
b23042andClaude Fable 5 d38b842e8d GhiVideo 소스 복제 — v4 작업 시작 기준
기존 GhiVideo 저장소 HEAD의 트래킹 소스 362개 파일을 복제.
(node_modules·storage·빌드 산출물·대용량 미디어는 .gitignore 규칙대로 제외)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 14:32:51 +09:00

7.2 KiB
Raw Permalink Blame History

POI 라벨 사라진 뒤 팝업 재등장 수정

소요 시간: 약 10분 Context 사용량: input ~80k / output ~3k tokens

문제

POI 라벨이 화면 밖으로 사라졌는데, 방금 전 위치에 POI 팝업이 다시 뜨는 문제.

원인

client/src/components/overlay/StationOverlay.tsx 의 마커 그리기 루프(1114행)가 라벨이 화면(비디오 크롭) 밖으로 나가도 cc.Zc >= CLIP_Z 이기만 하면 매 프레임 visStructRef 에 계속 등록.

  • 팝업 제거 로직(RAF): 라벨이 off-container 상태 4프레임이면 팝업 제거.
  • 자동 sync 인터벌(150ms): vis.has(title) 이 아직 true → 팝업을 즉시 다시 추가. → 둘이 싸워 팝업이 마지막 위치에 되살아남.

수정

visStructRef.set 을 팝업 off 판정과 동일한 화면 범위(px/py ∈ [-16, W+16]/[-16, H+16])로 제한. vx(nx)=offX+nx*dispW 는 팝업 off-check 의 sx=offX+disp.x*dispW 와 같은 좌표계라 일치. → 라벨이 화면 밖이면 vis 에서도 빠져 sync 가 되살리지 않음(제거 로직과 일관).

추가 수정 (잔여 1프레임 번쩍임)

visStructRef 가드만으론 sync 인터벌이 팝업 DOM 을 새로 mount 하는 순간의 번쩍임이 남음: 새 팝업 요소는 React 초기 스타일(top: sy+27)로 보이는 상태로 붙고, 다음 RAF 가 '화면 밖→숨김' 판정을 내리기 전 한 프레임 stale 위치에 번쩍임.

→ 팝업 ref 콜백에서 popupPosRef(RAF 위치 확정 시 기록)에 아직 없는 '신규' 팝업은 visibility:hidden 으로 mount. RAF 가 온스크린 확인 후에만 visible 로 전환(off 면 계속 숨김→제거). visibility 는 RAF 명령형으로만 관리(style prop 미포함)라 재렌더에도 유지됨.

추가 수정 2 (hidden-mount 가드 무력화 — 진짜 원인)

hidden-mount 가드는 !popupPosRef.has(id) 일 때만 숨긴다. 그런데 팝업 제거 경로가 여럿인데 보조 맵(popupPosRef/popupMissRef) 정리는 RAF off-4프레임 경로만 했음. → sync toRemoveIds / 수동(✕·ESC·빈곳클릭) 제거 시 popupPosRef 잔존 → 같은 title 재추가 시 popupPosRef.has=true → 숨김 안 됨 → React 초기 스타일(top: sy+27)로 보이는 채 mount → 라벨 없이 팝업만 stale 위치에 나타남(+ RAF 가 stale 위치에서 EMA 로 미끄러짐).

수정:

  1. infoPopups 변경 effect 에서 사라진 id 의 보조 맵 일괄 정리(단일 출처).
  2. sync toRemoveIds 처리 시 즉시 popupPosRef/popupMissRef 정리(재추가 전에 확실히 비움).

추가 수정 3 (definitive — 라벨 가시성과 완전 결합)

증상이 계속 동일해 근본 재분석: 팝업 off 판정이 라벨 십자마커 중심 ±16px 슬랙 + 화면 안 clamp 조합이라, 마커가 경계 근처(예: 오른쪽 끝 밖 ~16px)면 라벨 텍스트는 화면 밖(안 보임)인데 팝업만 clamp 되어 화면 안에 뜸 → "라벨 없이 팝업만".

수정: 이번 프레임에 라벨이 실제로 화면 안에 그려졌는지(히트박스∩뷰포트)를 onScreenLabels 집합으로 만들고, 팝업 표시/visStruct 등록을 이 집합에만 의존하도록 통일.

  • off = !disp || !onScreenLabels.has(id) (좌표 슬랙·clamp 기반 판정 제거)
  • visStruct 등록도 poiVisible(히트박스 온스크린) 기준

추가 수정 4 (사라졌다 잠깐 재등장 → 완전 제거)

증상: 라벨 사라짐 → 팝업 사라짐 → 잠깐 재등장 → 사라짐. 원인은 sync(150ms 추가) ↔ RAF(제거) 충돌.

  • 경계에서 라벨이 1~2프레임 깜빡이면 visStruct 재등록 → sync 가 팝업 재생성 → RAF 가 다시 제거.

수정:

  1. 추가 히스테리시스: 라벨이 연속 ADD_STREAK(6프레임 ~100ms) 이상 화면 안일 때만 visStruct 등록 (poiOnScreenStreakRef). 짧은 깜빡임으론 팝업 생성 안 됨.
  2. 제거 단일화: sync 의 팝업 제거 로직 삭제 → RAF(onScreenLabels + 4프레임 유예)가 유일한 제거 권한. add/remove 충돌 제거 → 라벨 사라지면 팝업도 사라진 뒤 재생성 안 됨.

추가 수정 5 (진짜 리그레션 원인 — flip 왕복)

사용자 지적: 이 증상은 '하단 겹침' 수정(추가수정 없음, 첫 위치 수정) 이후 생긴 리그레션. → 가시성 문제가 아니라 flip(아래↔위 전환) 로직에 히스테리시스가 없어서 발생. 라벨이 하단 flip 임계값 부근이면 드론 흔들림/평활로 sy 가 미세하게 오르내릴 때마다 ty+ph > H-2 가 참↔거짓 왕복 → 팝업이 아래↔위로 순간이동(bigJump 스냅) → "사라졌다 나타났다".

수정: popupFlipRef(위/아래 상태) + 히스테리시스(HYST=48px). '위'로 간 뒤엔 아래에 48px 이상 여유가 생겨야만 '아래'로 복귀 → 경계 왕복 제거. flip 상태도 제거 경로에서 정리.

추가 수정 6 (하단 이탈 즉시 제거 — 유예 삭제)

요구: 라벨이 하단으로 벗어나면 팝업 즉시 사라지고 재등장 금지. 잔여 원인: 제거에 4프레임 '유예'가 있어, 그 사이 흔들림으로 라벨이 경계로 잠깐 되들어오면 RAF 가 아직 안 지워진 팝업을 다시 visible 로 바꿈 → "사라졌다 잠깐 나타났다".

수정: off(라벨 화면 밖) 판정 시 유예 없이 즉시 toRemove(+숨김). 재등장은 sync 의 ADD_STREAK(6프레임 연속 안정) 게이트가 막음 → 짧은 복귀로는 재생성 안 됨. mid-screen 은 라벨 히트박스가 가장자리에서 멀어 off 가 안 나므로 영향 없음.

추가 수정 7 (하단 기준 = 영상 영역(스테이션바 위), 모니터 아님)

요구: 팝업이 스테이션바에 걸치지 않게, 라벨이 바 밑으로 내려가면 팝업 제거. 기준은 영상 재생 영역.

  • 스테이션바는 영상 위 z-20 오버레이(absolute bottom-0), 팝업은 z-40 → 바 위에 걸쳐 보였음.
  • 유효 하단 HB = H barHeight 도입(barHeightRef, ResizeObserver 실측 ~130px).
    • labelOnScreen 하단 기준 H-2HB-2: 라벨 히트박스가 바 밑이면 off → 즉시 제거.
    • 팝업 flip/clamp 하단 기준도 HHB: 팝업이 바 위로만 배치(걸침 방지).

추가 수정 8 (flip 제거 + 바 뒤로 슬라이드)

요구: 팝업이 라벨 위로 flip하면 안 됨. 항상 라벨 아래에 있다가 라벨이 바 밑으로 사라질 때 같이 사라짐.

  • flip/clamp 둘 다 문제였음(flip=위로 튐, clamp=바에 걸쳐 멈춤).

수정:

  1. flip·하단 clamp 제거 → 팝업 ty = sy + LABEL_HALF + GAP (항상 라벨 아래, 자유 하강).
  2. 팝업들을 클립 컨테이너로 감쌈: absolute left-0 right-0 top-0 overflow-hidden + bottom: barHeight (높이 = 영상영역 HB). 팝업이 라벨 따라 내려가면 바 영역에서 클립돼 '바 뒤로 슬라이드'되어 사라짐.
  3. 제거는 label off(HB 기준) 그대로 → 라벨이 바 밑이면 팝업 제거.

배포 확인

  • :55000 서빙 index.html → assets/index-yfJyCKp1.js (이번 빌드 해시와 일치)
  • ecosystem: ghiVideo, cwd=프로젝트루트, clientDist=client/dist (정적 즉시 반영)

검증

  • tsc --noEmit 통과
  • npm run build 성공 → :55000 프로덕션 반영 (브라우저 강력 새로고침 필요)