"어떤 개발자의 홈페이지에 들어갔더니, 작은 마을이 살아 숨쉬고 있었다."

이 비전을 실현하기 위해, 방문자 경험을 처음부터 끝까지 감사(audit)하고 다듬었다.

진단: 무엇이 경험을 깨뜨리고 있었나

3단계 방문자 경험(5초 관조 → 첫 방문 가이드 → 재방문 관계)의 관점에서 코드를 읽었을 때, 핵심 문제들:

  1. 재방문자가 첫 방문자와 동일한 플로우를 겪음 — 매번 이름 모달 + 인트로 재생
  2. 자동 저장 없음 — 탭을 닫으면 진행 상황 소실
  3. 인트로 건너뛰기 불가 — 첫 방문자도 답답할 수 있음
  4. LLM 실패 = 몰입 붕괴 — "나 말하는 법을 까먹은 거 같아..."가 반복
  5. 첫 방문자에게 조작 안내 없음 — 무엇을 해야 하는지 모름

변경 사항

1. 재방문자 즉시 입장

기존: 항상 이름 모달 표시 → 인트로 재생
변경: PLAYER_NAME_KEY + SAVE_KEY 존재 → 모달 건너뛰기, 자동 로드, 인트로 스킵

isReturningVisitor 플래그로 분기. 재방문자는 "다시 오셨군요, {name}님. 마을이 기다리고 있었어요." 메시지와 함께 바로 마을에 입장한다.

시간은 저장된 값이 아닌 현재 서울 시간으로 재설정 — "시간은 현실과 1:1" 원칙 준수.

2. 자동 저장

  • beforeunload 이벤트: 페이지 닫힐 때 자동 저장
  • 3분 주기 자동 저장
  • Firebase 메모리 동기화도 페이지 닫힐 때 실행

수동 저장 버튼은 그대로 유지.

3. 인트로 건너뛰기

intro-sequence.jsskip() 메서드 추가. 트리거:

  • 재방문자: 자동 스킵
  • 아무 키 입력: 인트로 중 키보드 누르면 스킵
  • 3D 캔버스 클릭: 터치/클릭으로 스킵

4. 유진 가이드 후속 대사

기존에는 유진이 한 마디 인사하고 끝. 이제:

  • 첫 인사 (LLM) → 3.5초 후 → 후속 대사 (LLM)
  • 첫 방문: "궁금한 거 있으면 편하게 물어보세요. 마을 구경도 해보세요!"
  • 재방문: "마을 근황을 한 문장으로" 전달

guide-greeting.jsroamWait도 8→12로 늘려서 유진이 인사 후 바로 떠나지 않도록.

5. 온보딩 힌트 시스템

첫 방문자 한정, 인트로 후 자연스러운 시스템 메시지:

  • 2초 후: "WASD로 이동, 마우스 드래그로 시점 회전" (모바일: 조이스틱 안내)
  • NPC 접근 시: "{name}이(가) 가까이 있어요! E키를 눌러 대화하세요"

재방문자에게는 표시하지 않음.

6. LLM 실패 인격화

기존: 모든 NPC가 "나 말하는 법을 까먹은 거 같아..."
변경: 성격 기반 랜덤 폴백
  - 도슨트: "어머, 잠깐 정신이 없었어요. 다시 한번 말해줄래요?"
  - 일반: "음... 뭐라고 해야 할지 모르겠어." / "잠깐, 생각 좀 해볼게..." / "아, 미안. 딴 생각하고 있었어."

E키 인사 LLM 실패도 동일하게 처리.

7. UI 다듬기

  • 모바일 '대화' 버튼: 근처에 NPC 없으면 disabled + 반투명 처리
  • 이름 모달: fade-in + slide-up 애니메이션 추가
  • 시스템 메시지 톤: 기술적 용어 제거
    • "LLM 채팅 활성화" → "주민들과 대화할 수 있습니다"
    • "LLM 엔드포인트 없음" → "주민들이 잠시 말을 아끼고 있어요"

수정 파일

파일 변경
main.js 재방문 분기, 자동저장, 온보딩 힌트, 인트로 스킵, LLM 폴백
intro-sequence.js skip() 메서드 추가
guide-greeting.js 후속 대사 체인, roamWait 연장
i18n.js 14개 새 키 (KO+EN): 환영, 힌트, 폴백
playground.css 모달 애니메이션, 버튼 disabled 스타일

결과: 3단계 방문자 경험

단계 이전 이후
5초 관조 모달이 가림 첫 방문만 모달, 재방문은 즉시 입장
첫 방문 인트로 후 방치 유진 인사 + 후속 안내 + 조작 힌트
재방문 매번 처음부터 자동 로드, "마을이 기다리고 있었어요", 현재 시간

마을은 이제 방문자를 기억하고, 기다리고, 맞이한다.