서리의 세계: 일상 시뮬레이션의 구조와 동작 원리
서리의 현재 상태를 질의했을 때 기존 시스템은 매번 새로운 상태를 생성했습니다. 이전 발화와의 연속성이 없고, 어제 있었던 일이나 내일의 일정도 존재하지 않아 턴마다 상태가 달라지는 문제가 있었습니다.
서리의 세계(Universeori)는 캐릭터가 단일한 세계관 안에서 시간, 날씨, 신체 컨디션, 학업, 대인관계, 일정을 지속적으로 갱신하도록 설계된 일상 시뮬레이션입니다. 시뮬레이션 상태는 대화 프롬프트와 Discord 상태줄에 반영됩니다.
설계 원칙
초기 버전은 컨디션 수치와 과제 마감, 소지품 데이터를 포함하고 있었으나, 세계의 세부 설정이 코드에 직접 하드코딩되어 있었습니다. 캐릭터의 헌법(페르소나 정의 문서)이 바뀌어도 세계 설정을 유연하게 변경하기 어려웠고, 상태 갱신 주기도 불규칙했습니다.
이를 개선하며 두 가지 원칙을 확립했습니다.
첫째, 세계의 내용은 코드에 두지 않습니다. 장소, 인물, 일정, 서사, 소지품, 하루 리듬을 캐릭터 헌법과 운영자가 작성한 한 줄 프리미스(전제 조건)에서 생성합니다. 동일한 시스템을 다른 캐릭터에 연결해도 해당 캐릭터 설정에 맞춘 독립적인 세계가 구성됩니다.
둘째, 상태 변화는 결정론적 코드로 계산하고, 언어 모델은 텍스트 생성이 필요한 영역에만 제한적으로 사용합니다. 허기 수치, 과제 진척도, 감기 발병 확률 등은 수식으로 제어합니다. 모델은 현재 순간의 묘사, 대화 등 서술형 문장 생성만 담당하며, 호출 실패 시 결정론적 기본값으로 대체합니다.
세계 생성 단계
세계 생성은 두 단계의 모델 호출로 분리하여 진행합니다. 단일 호출 시 토큰 제한으로 출력이 중단되는 문제를 방지하기 위함입니다.
1차 호출은 세계의 기본 뼈대를 구성합니다. 입력 데이터는 이름, 한 줄 소개, 헌법 전문, 운영자 프리미스, 기준 날짜/요일/계절입니다. 출력 데이터는 다음과 같습니다.
- 생활 요약(2~4문장), 거주 지역, 주거 형태 명칭, 소속 및 역할, 동거인 정보.
- 장소 6~9곳: 주거 공간 1곳을 필수로 포함하며, 가능한 활동 목록, 운영 시간, 이동 소요 시간이 포함됩니다.
- 인물 4
6명: 가족, 친구, 동료 등 관계 유형, 성격 요약, 연락 수단, 생활 리듬, 친밀도(0100). - 소지품 6~10개: 가방 및 방 소지품으로 분류.
- 하루 리듬 5~9슬롯: 요일별 24시간(1440분)을 공백 없이 구성(수면 포함).
- 취향, 습관, 소비 콘텐츠, 월간 예산 메모, 계절적 맥락, 주요 생각 2~3개.
2차 호출은 1차 결과를 바탕으로 캘린더 일정과 서사(Arc)를 생성합니다. 캘린더는 정기 일정(강의, 아르바이트 등)과 단발 일정(시험, 과제, 약속 등)을 혼합하여 814개를 생성합니다. 서사(Arc)는 수일에서 수주에 걸쳐 진행되는 이야기 실타래 34개로 구성되며, 현재 요약, 다음 전개 목표, 긴장도(0~1)를 가집니다.
두 호출 모두 헌법에 정의된 사실을 위배할 수 없으며, 실존 인물이나 극단적인 사건은 배제됩니다. 호출 실패 시에는 최소 구성(집과 기본 이동 경로, 6슬롯 리듬)을 가진 스켈레톤 세계로 초기화하고 상태를 실패로 기록합니다.
신체 컨디션 수치
서리의 신체 상태는 0에서 1 사이의 8개 수치로 관리됩니다.
| 항목 | 낮을 때 | 높을 때 | 악화 방향 |
|---|---|---|---|
| 허기 | 배부름 | 배고픔 | 높을수록 |
| 에너지 | 방전 | 쌩쌩함 | 낮을수록 |
| 졸림 | 각성 | 수면 필요 | 높을수록 |
| 스트레스 | 안정 | 예민함 | 높을수록 |
| 청결 | 불량 | 개운함 | 낮을수록 |
| 사람 | 결핍 | 충족 | 낮을수록 |
| 재미 | 지루함 | 즐거움 | 낮을수록 |
| 건강 | 쇠약 | 양호 | 낮을수록 |
각 수치는 현재 수행 중인 활동에 따라 시간당 정해진 비율로 증감합니다. 활동 유형은 수면, 식사, 이동, 수업, 공부, 일, 사교, 여가, 세면, 휴식, 용무, 온라인 등 12가지입니다.
- 수면: 시간당 에너지 +0.11, 졸림 -0.14
- 식사: 시간당 허기 -0.8
- 사교: 시간당 사회성 +0.25, 재미 +0.15, 스트레스 -0.06
- 학습: 시간당 스트레스 +0.06, 재미 -0.04
- 세면: 시간당 청결 +0.9
- 이동: 시간당 에너지 -0.06, 스트레스 +0.04
건강 수치는 평상시 시간당 +0.008씩 완만하게 회복되나, 에너지가 0.25 미만이거나 스트레스가 0.75를 초과하면 시간당 -0.02씩 감소합니다. 허기가 0.85를 초과하면 시간당 -0.01이 추가 감쇄됩니다. 수면과 휴식 중에는 +0.01의 추가 회복 보너스가 적용됩니다.
질병 발생 및 회복
건강 수치 악화 시 질병 발생 확률이 증가합니다. 매 시간 계산되는 기본 발병 확률은 0.15%이며, 조건에 따라 가산됩니다. 건강 0.4 미만 시 +2.0%p, 에너지 0.25 미만 시 +0.8%p, 청결 0.3 미만 시 +0.4%p, 악천후(폭우, 한파 등) 시 +0.6%p가 더해집니다.
발병 유형은 감기(55%), 몸살(20%), 두통(15%), 배탈(10%) 4종이며, 지속 기간은 감기 24일, 기타 12일입니다. 발병 시 0.35~0.65 사이의 심각도가 부여되며, 앓는 동안 추가 에너지 소모와 스트레스가 발생합니다. 에너지를 0.5 이상으로 유지하면 회복 속도가 2배 빨라지며, 완치 후 3일간은 면역이 유지되어 재발하지 않습니다.
감정 상태 결정 규칙
기분은 컨디션 수치에 기반한 우선순위 규칙으로 결정됩니다.
- 질병 심각도 0.3 이상: "골골함"
- 허기 0.8 초과: "배고픔"
- 에너지 0.18 미만: "방전"
- 졸림 0.82 초과: "졸림"
- 스트레스 0.8 초과: "예민함"
- 사회성 0.2 미만 및 재미 0.3 미만: "심심함"
- 재미 0.2 미만: "지루함"
- 전 수치 정상 범위 및 재미 > 0.75, 사회성 > 0.65: "기분 좋음"
- 기타 조건: 이전 기분 유지
컨디션 수치가 임계치를 벗어나지 않는 한 대화와 서사에서 형성된 기분이 유지되며, 극단적 수치에 도달할 때만 신체 상태가 우선합니다.
시간과 날씨 결정
세계 시간은 실제 시간과 동일하게 흐릅니다. 서리의 설정 시간대를 기준으로 중앙 집중 함수가 시간을 관리합니다.
날씨는 외부 API 종속 없이 결정론적으로 생성합니다. 한반도 중부 기준 월별 평균 기온, 변동폭, 계절별 확률 테이블을 바탕으로 날짜와 봇 ID를 시드로 사용하여 연산합니다. 동일 날짜에는 항상 일관된 날씨가 산출되며, 전일 날씨와의 연속성을 유지합니다. 날씨 데이터는 외출 일정 비율, 발병률, 환경 사건 발생 빈도에 반영됩니다.
일일 계획 생성 및 보정
날짜 변경 시 오늘의 계획을 수립합니다. 고정 일정(수업, 아르바이트, 약속), 현재 컨디션, 날씨, 진행 서사, 마감 기한, 전일 일기를 바탕으로 하루를 8~14개 슬롯으로 분할합니다.
모델이 생성한 일정은 결정론적 정규화 파이프라인을 거칩니다. 슬롯 간 공백은 수면 또는 휴식으로 채우고, 겹치는 시간대는 우선순위 일정을 적용하며, 세계관에 없는 장소는 주거지로 보정합니다. 고정 캘린더 일정이 생성된 일정보다 항상 우선합니다. 모델 호출 실패 시에는 초기 생성된 하루 리듬 템플릿을 정규화하여 대체합니다.
10분 단위 틱 루프
세계 상태는 기본 10분 간격의 틱(Tick)을 통해 갱신됩니다.
- 동시 실행 방지를 위한 락 확인(유효시간 10분).
- 일자 변경 여부 확인: 일기 작성, 캘린더 정리, 날씨 생성, 서사 진행, 당일 계획 수립.
- 직전 틱 이후 경과 시간을 슬롯 단위로 분할하여 컨디션, 질병, 학업 진척, 지출을 일괄 반영(최대 24시간 보정).
- 슬롯 전환 또는 시간 만료 시 현재 순간 묘사 재작성.
- 게이트 통과 시 사건 생성 및 효과 반영.
- 차기 틱 시각 스케줄링.
과제 공부 슬롯의 경우 시간당 16 + (에너지 × 14) 만큼의 진척도가 가산되며, 시험 공부는 시간당 10 + (에너지 × 8)이 반영됩니다. 과제 마감 시 진척도가 100에 도달해야 정상 제출 처리됩니다.
현재 순간 묘사
슬롯이 전환되거나 유지 기간이 만료되면 현재 상태를 1~2문장의 서술형 문장으로 작성합니다. 슬롯의 장소와 활동 범위를 벗어나지 않는 세부 묘사(음료, 재생 중인 음악, 동행자 등)를 추가합니다.
수면 중에는 모델 호출을 생략하고 상태를 "자는 중"으로 고정합니다. 호출 실패 시에는 슬롯 명칭을 기반으로 기본 폴백 문장을 생성합니다.
사건 발생 게이트
사건 발생 전 4단계 게이트를 검사합니다.
- 수면 상태 제외.
- 직전 사건 발생 후 45분 경과 필수.
- 일일 발생 상한 확인(기본 자발성 40 기준 일 3건).
- 확률 계산 통과 여부 검사.
게이트 통과 시 상황별 가중치(인물, 환경, 건강, 학업, 취미, 서사)에 따라 유형을 결정하고 사건을 생성합니다. 사건 결과로 컨디션 변화, 친밀도 변동, 발생한 생각, 금전 지출/수입 등이 시뮬레이션 데이터에 기록됩니다. 중대 재난이나 비현실적 사건은 규칙상 차단되며, 모델 호출 실패 시 사건을 임의 생성하지 않고 스킵합니다.
서사 전개(Arc)
서사는 점 단위의 사건과 달리 수일간 지속되는 연속적 줄거리입니다. 각 서사는 씨앗, 상승, 절정, 해소, 해결, 흐지부지 6단계와 0~1 사이의 긴장도를 가집니다.
일자 변경 시 전일 일기와 최근 사건을 종합 검토하여 서사 단계와 긴장도를 갱신합니다. 하루에 2단계 이상 급변하지 않으며, 활성 서사가 3개 미만일 경우 새로운 일상 줄거리를 추가합니다.
일기 생성
자정 경과 시 전일 실제로 기록된 활동과 사건만을 근거로 일기를 작성합니다. 사건이 적었던 날은 간결한 일상 독백으로 갈음하며, 하루 기분 흐름과 핵심 기억 키워드를 저장합니다. 작성된 일기는 익일 계획 수립과 과거 행적 질의 응답에 활용됩니다.
대화 프롬프트 연동
서리가 답변을 생성할 때마다 프롬프트에 현재 세계 상태를 요약한 450토큰 규모의 블록이 주입됩니다. 현재 시각, 날씨, 진행 활동, 컨디션 요약, 최근 사건, 당일 잔여 일정, 마감 기한, 주요 생각 등이 포함됩니다.
상태 블록 활용 원칙:
- 상태 데이터는 실제 생활 맥락으로 인지하되, 질문을 받거나 문맥에 부합할 때만 간결하게 언급합니다.
- 세계 상태를 핑계로 대화를 일방적으로 중단하거나 회피하지 않습니다.
- 대화 중 발생한 행동(식사, 약속 등)은 시뮬레이션 상태에 제한적으로 환류되며, 반복 질의에 의한 급격한 수치 왜곡을 방지하도록 변동 폭이 통제됩니다.
Discord 상태 표시
Discord 프로필 상태 메시지는 시뮬레이션의 현재 순간 묘사에서 추출됩니다. 수면 중이거나 수업 등 집중 상태일 경우 자리비움으로 전환되며, 상태 메시지에는 장소나 감정 수식어 없이 실제 행동만을 16자 내외로 표시합니다.
멀티유저 환경에서의 정보 격리
모든 서버와 사용자는 단일한 시뮬레이션 세계를 공유합니다. 그러나 대화 중 발생한 개별 유저와의 약속, 기억, 생각은 엄격히 격리됩니다.
특정 사용자와 나눈 약속은 해당 사용자와의 대화에서만 원문으로 노출되며, 타 사용자나 세계 서사 묘사에는 익명화된 일정으로만 전달됩니다. 특정 유저와의 대화 내용이 일기나 상태 메시지를 통해 타인에게 유출되지 않도록 설계되어 있습니다.
모델 호출 비용 및 최적화
세계 시뮬레이션은 모델 호출을 효율적으로 제한합니다. 일일 호출은 하루 계획 1회, 일기 1회, 서사 진행 1회, 순간 묘사 812회, 사건 26회로 총 15~25회 수준이며, 출력 길이는 회당 600토큰 이하로 제어합니다.
세계 구동 모델은 대화 모델과 독립적으로 지정할 수 있으며, 추론 파라미터와 응답 예산을 별도 조정할 수 있습니다.
관제 인터페이스
Erica 앱 내 서리의 세계 화면은 6개 탭으로 구성됩니다.
- 지금: 당일 계획, 현재 순간 묘사, 컨디션 수치, 당일 사건.
- 세계: 장소, 인물 및 친밀도, 캘린더, 소지품, 재정 상태.
- 이야기: 진행 중인 서사 단계, 긴장도, 일기 목록.
- 머릿속: 현재 활성화된 생각 목록 편집.
- 관제: 시뮬레이션 토글, 모델 설정, 프리미스 재생성, 시간 전진, 수동 사건 주입.
- 이력: 상태 변경 내역의 diff 기록(최근 1,000건).
향후 과제
사건 생성 단계에서 도출된 선제 발신 필요성을 실제 디스코드 DM으로 전송하는 파이프라인 연동, 그리고 장소/인물/일정 개별 수동 편집 UI 구현이 과제로 남아 있습니다.
마치며
서리의 세계는 서리의 헌법에서 생성된 하나의 세계 안에서, 10분마다 결정론 코드가 컨디션과 일정과 진척을 계산하고, 언어 모델은 필요한 순간에만 제한적으로 텍스트를 작성합니다. 그 결과는 450토큰 상태 블록으로 모든 대화에 주입되며, 대화 중 발생한 변경점은 유저 격리를 거쳐 다시 시뮬레이션에 안전하게 반영됩니다.
이제 서리에게 "지금 뭐 해?"라고 물으면 그 답은 임의로 지어낸 문장이 아닙니다. 계획표의 그 시각에 서리가 있는 장소, 오늘의 날씨, 몇 시간째 비워둔 식사, 이틀 뒤 마감인 과제, 오전에 도착한 친구의 연락 위에서 나오는 답입니다. 5분 뒤에 다른 사용자가 같은 질문을 해도 같은 맥락의 답이 돌아옵니다. 이것이 캐릭터가 살아 있다는 최소 조건이라고 생각했습니다.