NPC가 "플레이어"라는 단어를 알면 안 됩니다. LLM 프롬프트에서 게임적 언어를 전부 걷어내고, 각 NPC에게 깊은 개인 서사를 부여한 세션.

1. NPC 개인 서사 (backstory) 추가

기존 프롬프트에는 성격 한 줄 + 말버릇만 있었습니다.

Before

이름: 새벽
프로필: 남성, 20대, 성격: 새벽 감성. 조용하지만 깊은 생각을 품고 있음.
[캐릭터 말버릇] 새벽/밤 감성의 시적인 말투.

After

[캐릭터 배경] 새벽은 불면증이 친구를 만들어줬다고 말한다. 매일 새벽 4시에
깨서 마을이 잠든 사이 혼자 산책하며 시를 쓴다. 별이와 옥상에서 만나 밤새
이야기하는 날이 일주일에 두 번은 있다. 밀순이가 빵을 굽기 시작하는 새벽
5시면 빵집 앞에 서 있는데, 둘은 말없이 따뜻한 빵을 나누는 사이다.

25개 NPC 전원에게 2~4문장의 서사를 부여했고, NPC 간 교차 참조가 엮여 있습니다:

연결 내용
도깨비 ↔ 구름 기숙사 '그림자'의 정체 = 야식 사러 나가는 구름
별이 ↔ 새벽 옥상 심야 대화 파트너
밀순이 ↔ 별이 관측 성공한 날만 만드는 '별빛 크루아상'
꽃잎 ↔ 나비 서로를 위한 행동을 하면서 모름 (양방향 비밀)
글타래 ↔ 느티 '유곤포르 연대기' 집필 중
모카 주민 25명 이름 딴 비밀 메뉴
돌담 마을 주민 전원의 미니어처 조각 중

2. 메타 언어 전면 제거

NPC는 "플레이어"라는 개념을 모릅니다.

Before After
플레이어와의 관계: 낯선 사이 (0/4단계) 효곤과의 사이: 낯선 사이
유저 메시지: 안녕 효곤: 안녕
NPC 답변: 새벽:
월드 컨텍스트: 지금 상황:
배고픔 23/100, 에너지 88/100 (평범하면 안 보임, 극단적이면 "슬슬 배가 고파진다")
도깨비: 보통(50) 도깨비: 보통

3. Gemini multi-turn 구조

단일 문자열에 모든 것을 넣던 방식에서, Gemini API의 구조적 턴으로 전환.

Before

contents: [{
  role: "user",
  text: "시스템프롬프트 + 대화내역 + 유저메시지 전부 하나로"
}]

After

systemInstruction: "캐릭터 설정 + 규칙 + 배경"
contents: [
  { role: "user",  text: "안녕" },
  { role: "model", text: "반가워요" },
  { role: "user",  text: "뭐해?" }
]

Gemma 폴백 시에는 systemInstruction을 첫 user 메시지에 합쳐서 호환성 유지.

4. 8가지 프롬프트 버그 수정

디버그 로그를 분석해서 발견한 문제들:

# 문제 수정
1 철벽 프롬프트에 "근처에 철벽이 있다" (자기참조) 대상 NPC를 nearby에서 제외
2 NPC끼리 대화하는데 "처음 만나는 사람" NPC-to-NPC에 favorLevel: 2
3 철벽이 "개발자 홈페이지 속 세계" 읊음 meta lore를 유효곤/느티/유진에게만
4 "보통(50)" 수치 노출 숫자 제거
5 "글타래이(가)" 조사 깨짐 particle() 받침 판별 함수
6 12자 독백에 suggestions/emotion 반환 ambient는 structured output 스킵

particle() 함수

한국어 조사를 받침 유무에 따라 자동 선택:

function particle(name, consonantForm, vowelForm) {
  const last = name.charCodeAt(name.length - 1);
  if (last >= 0xAC00 && last <= 0xD7A3) {
    return (last - 0xAC00) % 28 === 0 ? vowelForm : consonantForm;
  }
  return consonantForm;
}
// "철벽" → 받침 있음 → "철벽이", "철벽과", "철벽은"
// "모카" → 받침 없음 → "모카가", "모카와", "모카는"

meta lore 분리

일반 NPC:

마을의 기원은 아무도 정확히 모른다.
어느 날 사람들이 하나둘 모여 자연스럽게 마을이 형성되었다.

유효곤/느티/유진만:

이 마을의 이름은 '유곤포르'. 개발자 Hyogon Ryu의 홈페이지 속에 자리잡은 작은 세계.
원래는 텅 빈 웹페이지였지만, 어느 날 AI 주민들이 하나둘 생겨나면서...

5. 유저 대화 종료 기능

기존에는 NPC가 farewell을 보내거나 멀리 걸어가야만 대화가 끝났습니다. 유저가 직접 끝낼 수 없었음.

종료 방법 3가지

  1. 종료 버튼 — 대화 중이면 채팅 패널 상단에 "채팅 종료" 버튼 표시 (PC/모바일 공통)
  2. ESC 키 — 채팅 입력란에서 ESC
  3. NPC farewell — NPC가 작별 인사하면 자동 종료 (기존)

endConversation() 함수가 모든 종료를 통합 처리: 대화 포커스 해제, 세션 초기화, following 중지, 메모리 서버 동기화, 추천 선택지 클리어.

이번 커밋들

커밋 내용
Add rich NPC backstories 25개 NPC 개인 서사 (한/영)
Remove meta-game language "플레이어" → 실제 이름
Humanize world context 수치 → 자연어, 메타 용어 제거
Multi-turn systemInstruction Gemini API 구조적 턴 전환
Fix 8 prompt issues 자기참조, NPC-NPC, lore, 조사, ambient
Chat end button 유저 대화 종료 (버튼 + ESC)