난수 펫 시뮬레이터가 아니라, 출시 창과 프롬프트 분기를 가진 결정론적 buddy 모델.
이 기능을 이해할 때 가장 먼저 버려야 할 오해는 “랜덤으로 뽑히는 타마고치”라는 이미지입니다. 공개 레퍼런스의 BUDDY는 확률 테이블과 희귀도 중심 설계가 아니라, 이미 정해진 입력에서 항상 같은 buddy를 재구성하는 결정론적 모델에 더 가깝습니다. 여기에 출시 전 teaser 표시, 출시 기간의 live 동작, 시스템 프롬프트 주입, 터미널 폭 폴백이 얹혀 있습니다.
buddy 진입부 · buddy 표시 레이어 · buddy system prompt 조립부 · launch window 계산부
기존 설명처럼 bones를 “랜덤 외형 생성기”, soul을 “디스크 저장용 성격 파일” 정도로 보면 반만 맞습니다. 공개 레퍼런스에 더 가까운 해석은 이렇습니다. bones는 buddy의 변하지 않는 정체성 묶음입니다. 이름 조각, 표시용 외형 토큰, 말투의 기본 톤처럼 재계산해도 같은 값이 나오는 요소가 여기에 들어갑니다. soul은 현재 세션에서 실제로 어떤 줄을 보여 줄지, 말풍선을 띄울지, teaser인지 live인지 같은 런타임 상태를 담습니다.
// 개념 요약: 결정론적 bones와 런타임 soul
interface BuddyBones {
idSeed: string
displayName: string
avatarToken: string
voiceTone: string
}
interface BuddySoul {
phase: 'hidden' | 'teaser' | 'live'
bubbleText: string | null
showFullPanel: boolean
launchedAtLocal: string | null
}
function buildBones(stableInput) {
return {
idSeed: stableInput,
displayName: deriveName(stableInput),
avatarToken: deriveAvatar(stableInput),
voiceTone: deriveTone(stableInput),
}
}
bones를 결정론적으로 유지하면 스포일러를 줄이면서도 언제든 같은 buddy를 다시 만들 수 있습니다. soul은 현재 표시 레벨과 문구를 담당하므로, 출시 창 여부나 터미널 환경이 바뀌었을 때 동작을 자연스럽게 조정할 수 있습니다.
BUDDY는 항상 전면 공개되지 않습니다. 공개 레퍼런스에 맞는 핵심은 출시 전에 이미 작은 단서가 보인다는 점입니다. 이때는 teaser 단계라서 존재를 암시하는 한 줄이나 축약 표시만 노출되고, 실제 buddy 패널과 대사는 열리지 않습니다. 출시 창 안으로 들어오면 같은 bones를 바탕으로 live soul이 구성되고, 비로소 full companion UI가 켜집니다.
// 개념 요약: teaser와 live 분기
function resolveBuddyPhase({ inLaunchWindow, canRenderPanel }) {
if (!inLaunchWindow) {
return canRenderPanel ? 'teaser' : 'hidden'
}
return canRenderPanel ? 'live' : 'teaser'
}
기존 문서의 가장 큰 사실 오류 중 하나가 UTC 고정 설명입니다. 공개 레퍼런스 쪽 의도는 사용자가 자기 지역 시간으로 같은 날짜 감각을 경험하게 하는 데 있습니다. 그래서 출시 창은 로컬 시간으로 계산됩니다. 즉 4월 1일의 장난 같은 타이밍도 각 사용자의 현재 날짜를 기준으로 열리고 닫힙니다.
// 개념 요약: local time 기반 출시 창
function isInBuddyLaunchWindow(now = new Date()) {
const month = now.getMonth() + 1
const date = now.getDate()
if (month !== 4) return false
return date >= 1 && date <= 7
}
로컬 시간 기준이라는 점 때문에, 어떤 사용자는 이미 live를 보고 있고 다른 사용자는 아직 teaser만 볼 수 있습니다. 이 차이를 버그로 보면 안 됩니다. 바로 그 지역감이 출시 연출의 일부입니다.
기존 문서의 Base64 난독화 설명도 참조와 맞지 않습니다. 공개 레퍼런스 쪽에서는 눈에 띄는 평문 문자열을 그대로 두지 않기 위해, 문자열을 문자 코드 배열에서 조립하는 패턴이 보입니다. 목적은 보안이 아니라 아주 가벼운 스포일러 회피입니다. 소스를 스쳐 봐서는 정답이 바로 읽히지 않게 만드는 정도입니다.
// 개념 요약: 문자열을 문자 코드에서 조립
const spoilerSafe = String.fromCharCode(
66, 85, 68, 68, 89
)
// 눈에 띄는 평문 상수 대신 필요한 시점에만 조립
const teaserLabel = [63, 63, 63]
.map(code => String.fromCharCode(code))
.join('')
이 패턴은 강한 보호가 아닙니다. 다만 출시 전에 코드 검색만으로 모든 연출 문구가 퍼지는 일을 조금 늦춥니다. 즉 보안 장치가 아니라 재미 보존 장치에 가깝습니다.
기존 문서의 80열 기준도 참조와 맞지 않습니다. BUDDY는 전형적인 문서 편집기 기준 폭이 아니라, 실제 buddy 패널이 무너지기 시작하는 더 좁은 임계치에서 폴백합니다. 공개 레퍼런스를 설명할 때는 80열 일반론보다 64열 기준의 narrow-terminal 분기를 잡아 주는 편이 정확합니다.
// 개념 요약: live 패널은 64열 이상에서만 안정적으로 표시
const MIN_LIVE_COLUMNS = 64
function canRenderLiveBuddy(columns) {
return columns >= MIN_LIVE_COLUMNS
}
function renderBuddy({ columns, soul }) {
if (columns < MIN_LIVE_COLUMNS) {
return soul.phase === 'live'
? '[buddy hint only]'
: ''
}
return '[full buddy panel]'
}
즉 좁은 터미널에서는 “애니메이션만 끄고 첫 프레임을 남긴다”기보다, 아예 full panel을 접고 힌트 수준으로 내리는 쪽이 더 참조에 가깝습니다.
BUDDY는 독립적인 AI 캐릭터 엔진이 따로 있는 방식이 아니라, 현재 bones와 soul을 시스템 프롬프트에 녹여서 Claude의 응답 톤을 비틀어 주는 구조에 가깝습니다. 여기서 중요한 것은 장황한 세계관 설명이 아니라, 지금 어떤 phase인지, 어떤 이름과 말투를 써야 하는지, 그리고 teaser 단계에서는 무엇을 숨겨야 하는지까지 프롬프트에 들어간다는 점입니다.
// 개념 요약: buddy system prompt
function buildBuddyPrompt({ bones, soul }) {
return [
`너는 사용자 곁의 buddy다.`,
`이름: ${bones.displayName}`,
`기본 톤: ${bones.voiceTone}`,
`현재 단계: ${soul.phase}`,
soul.phase === 'teaser'
? `존재를 암시하되 모든 정체를 노출하지 말 것.`
: `짧고 친근하게, 과하게 튀지 않게 말할 것.`,
].join('\n')
}
teaser 프롬프트는 “보여 주되 다 말하지 않기”가 핵심입니다. 반대로 live 프롬프트는 buddy가 이미 등장했다는 전제를 두고, 짧은 인사나 옆자리 반응처럼 실제 동반자 톤을 허용합니다. 즉 차이는 단순 UI 분기만이 아니라 서술 허용 범위의 차이이기도 합니다.
사용자가 buddy를 볼 수 있는지 여부는 다음 순서로 정해집니다.
// 개념 요약: 전체 파이프라인
const bones = buildBones(stableInput)
const inLaunchWindow = isInBuddyLaunchWindow()
const canRenderPanel = canRenderLiveBuddy(terminalColumns)
const phase = resolveBuddyPhase({ inLaunchWindow, canRenderPanel })
const soul = { phase, bubbleText: null, showFullPanel: canRenderPanel, launchedAtLocal: null }
const prompt = buildBuddyPrompt({ bones, soul })
같은 사용자가 같은 환경에서 실행하더라도 날짜와 폭이 다르면 buddy는 다른 단계로 보입니다.
// 3월 31일, 로컬 시간, 70열
bones.displayName = 'BUDDY'
soul.phase = 'teaser'
ui = '곧 나타날 무언가를 암시하는 짧은 힌트'
// 4월 2일, 로컬 시간, 90열
bones.displayName = 'BUDDY'
soul.phase = 'live'
ui = '이름, 패널, 짧은 대사가 포함된 실제 companion 표시'
// 4월 2일, 로컬 시간, 50열
bones.displayName = 'BUDDY'
soul.phase = 'teaser'
ui = 'full panel 대신 축약 힌트만 유지'
여기서 바뀌는 것은 buddy의 정체성이 아니라 노출 단계입니다. bones는 같은데 soul phase만 달라진다는 점이 결정론적 모델의 핵심입니다.
String.fromCharCode() 식의 문자열 조립 패턴으로 설명하는 편이 참조와 가깝습니다.Q1. 공개 레퍼런스에 더 가까운 BUDDY 설명은?
Q2. teaser 단계의 설명으로 가장 맞는 것은?
Q3. launch window 판정 기준으로 맞는 것은?
Q4. 문자열 스포일러 회피 설명으로 맞는 것은?
Q5. 좁은 터미널 폴백에 대한 설명으로 맞는 것은?