
부업 엔지니어가 Habitica에서 좌절한 이야기와 자신을 위해 만든 「재촉하지 않는 RPG」의 직업 판정 설계
요약
Habitica의 압박감에서 벗어나기 위해 설계된 '재촉하지 않는 RPG' ALTER의 직업 판정 로직을 소개합니다. LLM 대신 설명 가능한 가중치 스코어링(Explainable Weighted Scoring) 방식을 사용하여 사용자의 행동 데이터를 기반으로 직업을 자동 제안합니다.
핵심 포인트
- 사용자가 직업을 선택하지 않고 행동 결과로 결정되는 시스템 설계
- 10개 지표와 6개 직업 간의 가중치 행렬을 활용한 스코어링
- 변화하는 사용자의 상태를 반영하여 매달 직업이 바뀔 수 있는 유연성
- LLM 대신 설명 가능한 가중치 기반의 판정 로직 적용
1년여 전, 저는 Sako 씨(@sako_brain)의 이 포스트를 보았습니다.
정말 살이 빠질 것 같은 게임 × 부업 × 피트니스 앱 같은 것을 AI로 만들고 싶다
당시의 저는 다이어트도 부업도 「지속하지 못하는」 쪽의 사람이었습니다. Habitica에도 손을 댔지만, 하지 않은 날에는 HP가 줄어들고, 퀘스트가 쌓여, 결국에는 「앱을 여는 것이 두려운」 상태가 되었습니다. 계속하고 싶어서 시작했는데, 지속하지 못하는 자신을 매일 마주해야 하는 장소가 되어 있었습니다.
그래서 「재촉하지 않는 RPG」를 저 자신을 위해 만들었습니다. 본 기사에서는 그 핵심 — 행동으로부터 직업을 판정하는 로직 — 을 코드와 가중치 행렬(Weight Matrix)을 공개하며 설명합니다. 마케팅적으로는 「AI 판정」이라고 부르고 있지만, 내용은 LLM이 아니라 **설명 가능한 가중치 스코어링 (Explainable Weighted Scoring)**입니다. 그 부분까지 포함하여 솔직하게 작성하겠습니다.
ALTER는 부업의 commit과 다이어트의 발걸음을 동일한 한 명의 캐릭터에 실어 키우는 Web 앱입니다. 설계의 뿌리에는 「재촉하지 않는다·경쟁시키지 않는다·서두르게 하지 않는다」를 두고 있습니다 (사상의 상세한 내용은 별도 기사에 양보하겠습니다).
그 사상을 가장 구체적으로 나타내는 것이 직업 시스템입니다. 포인트는 하나입니다. 사용자에게 직업을 선택하게 하지 않는 것입니다. 「당신은 전사 타입인가요? 마법사 타입인가요?」라고 처음에 선택하게 하면, 사람은 「되고 싶은 모습의 자신」을 선택하게 되어 실제 행동과 괴리가 생깁니다. 어긋난 이상을 짊어지게 되면, 달성하지 못한 날이 「실패」로 보이게 됩니다.
그래서 ALTER는 처음 7일간의 행동을 관찰하여, AI 측에서 「당신은 이런 기색이 있군요」라고 제시합니다. 선택하는 것이 아니라, 지속한 결과로서 나타나는 것입니다. 이것이 「재촉하지 않는다」를 직업 시스템으로 번역한 형태입니다.
판정 입력은 매일 1분 동안 기록하는 10개의 지표입니다.
- 다이어트 계열: 체중 기록 유무 / 걸음 수 / 단백질 / 운동 / 공복 조절 / 수면
- 부업 계열: 집중 시간 / 아웃풋(Output) 수 / 영업 단계 / 매출
이 10개 지표의 7일 치 데이터를 통해, 6개 직업(검사 / 마법사 / 검사 / 상인 / 연금술사 / 비서) 각각의 「직업다움 스코어」를 계산하여 가장 높은 직업을 제시합니다. 확정되기 전까지는 「견습생」으로서 중립적인 동반자가 붙습니다.
중요한 설계 판단은 「월마다 바뀌어도 좋다」는 점입니다. 이번 달은 집중 시간이 늘어나 마법사, 다음 달은 영업 활동이 활발해져 상인. 고정하지 않음으로써 변화하는 자신을 부정하지 않는 구조로 만들었습니다.
판정의 심장은 6개 직업 × 10개 지표의 가중치 행렬입니다. 각 셀은 「그 직업다움」의 계수로, **1.0이 중용, 1.5가 『직업답다』, 0.5가 『직업답지 않다』**를 의미합니다.
| 지표 | 검사 | 마법사 | 검사 | 상인 | 연금술사 | 비서 |
|---|---|---|---|---|---|---|
| 체중 기록 | 0.5 | 0.3 | 1.0 | 0.3 | 1.5 | 1.0 |
| 걸음 수 | 1.5 | 0.5 | 0.8 | 0.5 | 0.7 | 0.8 |
| 단백질 | 1.3 | 0.8 | 0.8 | 0.5 | 1.5 | 0.8 |
| 운동 | 1.5 | 0.5 | 0.8 | 0.5 | 0.8 | 0.8 |
| 공복 조절 | 0.8 | 1.0 | 0.8 | 0.8 | 1.5 | 0.8 |
| 수면 | 1.0 | 1.0 | 0.8 | 1.0 | 1.0 | 1.0 |
| 집중 시간 | 0.7 | 1.5 | 1.0 | 1.0 | 0.5 | 1.0 |
| 아웃풋 | 0.7 | 1.3 | 1.2 | 1.0 | 0.5 | 1.0 |
| 영업 단계 | 0.7 | 0.5 | 0.8 | 1.5 | 0.5 | 1.0 |
| 매출 | 0.5 | 0.5 | 0.8 | 1.5 | 0.5 | 1.0 |
읽는 법은 간단합니다. 걸음 수와 운동이 늘어나면 검사, 집중과 아웃풋이 늘어나면 마법사, 영업과 매출이 움직이면 상인, 식사와 체중 관리를 정성껏 하면 연금술사. 검사와 비서만은 특징이 뚜렷하지 않으며, 후술할 bonus로 「직업다움」을 표현합니다.
// lib/character/class-weights.ts (발췌)
export const CLASS_WEIGHTS: Record<CharacterClass, Record<MetricKey, number>> = {
swordsman: { weight_logged: 0.5, steps: 1.5, protein: 1.3, exercise: 1.5, /* ... */ },
...
판정 본체는 부작용(Side Effect)이 없는 순수 함수(Pure Function)입니다. 흐름은 다음과 같습니다.
먼저 정규화(Normalization)를 진행합니다. 생(Raw) 데이터를 그대로 더하면 단위가 제각각이기 때문에, 각 지표를 0~1 사이로 맞춥니다. 기준값에 설계 철학이 담겨 있는데, 예를 들어 운동은 WHO의 주 150분(하루 30분)을 1.0으로, 걸음 수는 8,000보를 1.0으로 설정했습니다.
// lib/character/judge-class.ts (발췌)
function normalizeMetrics(input: DailyInputForJudge): Record<MetricKey, number> {
return {
...
점수는 「정규화 값 × 가중치」를 7일 × 10개 지표에 대해 모두 더하기만 하면 됩니다. LLM(거대언어모델)도 난수도 사용하지 않습니다. 동일한 입력이라면 반드시 동일한 직업이 나오는, 결정론적(Deterministic)이고 설명 가능한(Explainable) 계산입니다.
for (const cls of ALL_CLASSES) {
let s = 0;
for (const row of normalized) {
...
마지막으로 두 가지 보너스(Bonus)를 적용합니다. 가중치 행렬만으로는 특징이 뚜렷하게 드러나지 않았던 검사와 비서를 여기서 「행동의 질」로 포착합니다.
검사(Investigator): 7일 × 10개 지표의 총 70개 셀에 모두 기록이 있다면 (즉, 매일 빠짐없이 기록했다면) +4.5. 관찰과 일관성의 직업.
비서(Secretary): 각 지표의 7일 합계 표준편차(Standard Deviation)가 1.5 미만이라면 (즉, 어느 것 하나 치우침 없이 균등하게 수행했다면) +2.5. 균형의 직업.
if (allCellsLogged(normalized)) scores.investigator += INVESTIGATOR_CONSISTENCY_BONUS; // 4.5
if (totalsSdBelowThreshold(normalized, 1.5)) scores.secretary += SECRETARY_BALANCE_BONUS; // 2.5
마지막으로 내림차순 정렬하여 1위를 제시하고, 2·3위는 「다른 기운」으로서 덧붙입니다. "당신은 검사입니다. 다만, 마법사의 기운도 느껴집니다"라고 답할 수 있는 이유는 모든 직업의 점수를 보유하고 있기 때문입니다.
직업을 한 번 결정하면 고정하는 방식으로도 만들 수 있었습니다. 하지만 그것은 「재촉하지 않는다」는 철학에 어긋납니다.
사람의 생활은 달(Month)을 기준으로 변합니다. 바쁜 시기에는 부업이 멈추고 운동이 늘어납니다. 여유가 생기면 다시 책상 앞에 앉습니다. 고정된 직업은 변화한 자신을 「나답지 않다」며 부정하게 만듭니다. 그래서 ALTER는 매달 이 판정을 다시 수행하여, 직업이 슬라이드(Slide)하며 변할 수 있도록 설계했습니다. 지난달과 다른 직업이 되더라도, 그것은 퇴보가 아니라 단순한 변화입니다.
구현 측면에서도 판정이 점수와 가중치만으로 완결되는 순수 함수이기에, 월간 배치(Batch)로 몇 번을 돌려도 부작용이 없습니다. 「설명 가능하다는 것」은 사용자에게 성실함을 보이는 것인 동시에, 운영의 안심을 주는 요소이기도 합니다.
직업 판정은 자신의 행동을 부정하지 않기 위한 장치입니다. 강요하지 않고, 관찰하며, 변화를 허용합니다. 「지속하지 못하는 자신」에게 조금 지쳐 있다면, 한번 들여다봐 주세요.
Free (¥0): 6개 직업 판정 및 AI 동행 기능까지 사용 가능
Founders (월 ¥500 · 영구 락인(Lock-in), 50명 한정): Pro의 모든 기능을 반값에. 인원 충족 시 일반 Pro(¥980)로 전환됩니다
→ ALTER: https://alter.ponfreelance.com/?utm_source=qiita&utm_campaign=alter-launch&utm_content=q2
→ 모티프가 된 Sako 님의 포스트: https://x.com/sako_brain/status/2054790607076499585
아키텍처 전체와 AI 모델의 활용 구분은 별도 기사(Qiita)에, 결제의 영구 Lock-in 구현은 Zenn에 작성해 두었습니다.
저자: 폰(@pon_freelance)
AI 자동 생성 콘텐츠
본 콘텐츠는 Qiita AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기