내 작은 왕국 기획 위키

미션과 출석

01 게임

이 문서 안에서

플레이어가 무엇을 하면 무엇을 받는지를 정합니다. 일일·주간·월간 미션, 업적, 28일 접속 캘린더 다섯 가지입니다. 미션만을 위한 이벤트 추적기를 하나도 두지 않고, 코어 루프가 세이브에 쌓아 둔 평생 카운터를 그대로 읽습니다.

한 줄 요약 — 미션은 진행도를 저장하지 않습니다. 평생 카운터에서 기간 시작값을 뺀 값이 진행도이고, 리셋은 화면을 열 때 문자열을 비교해 알아냅니다.


1. 다섯 개의 목록

종류period리셋개수
일일 접속별도 캘린더하루 1회 수령28일 1주기
일일 미션"daily"00:00 KST9종(8종 + 전체 완료 보너스)
주간 미션"weekly"월요일 00:00 KST8종
월간 미션"monthly"매월 1일 00:00 KST6종
업적"once"없음48종(6분류)

전체 71종. 카탈로그를 넓힐 때 지킨 두 가지 규칙:

  1. 새 필드를 요구하지 않습니다. 모든 지표는 평생 stats 카운터이거나 세이브에서 즉석에서 파생됩니다. 그래서 전부 옛 세이브에도 소급 적용됩니다.
  2. 업적은 사다리로 만듭니다. 평면 12종은 2주면 바닥나고, 남은 것이 없는 업적 탭은 다시 열리지 않습니다. 채집 1,000 → 10,000 → 100,000 → 500,000처럼 몇 달을 걸쳐 걷는 실을 만듭니다.

2. 진행도 — 평생 카운터와 baseline

세이브 행: { id, period(기간 키), baseline(기간 시작 시 metric 값), claimed }.

progress = clamp(def.metric(save, now) - baseline, 0, target)

metricnow를 함께 받습니다. 대부분 무시하지만, 이미 기간에 한정된 지표(오늘 수령한 일일 미션 수)가 하나 있어서 필요합니다.

baseline = 0이 되는 경우는 두 가지이고, 한 함수(measuresFromZero)가 답합니다:

경우
period === "once"목표가 평생 누계. baseline을 잡으면 주민 5명인 왕국에 0/5를 영원히 보여 줍니다
absolute: true지표가 이미 오늘 것만 셈. baseline을 또 빼면 오전이 오후를 깎아 자정까지 달성 불가가 됩니다

absolute를 쓰는 것은 현재 d_all_clear 하나입니다. 롤오버도 같은 함수를 읽으므로, 옛 빌드가 남긴 0이 아닌 baseline은 다음 syncMissions에서 0으로 지워집니다.

2.1 미션이 읽는 지표

평생 stats 카운터 8종:

카운터대표 미션
resourcesGatheredd_gather_150 w_gather_2000 m_gather_12000 a_gather_* 4단
goldEarnedd_collect_gold_200 m_gold_5000 a_gold_* 3단
itemsCraftedd_craft_3 w_craft_20 m_craft_80 a_craft_* 4단
stagesClearedd_stage_2 w_stage_15 m_stage_60 a_stage_* 4단
foodEatend_feed_1 w_feed_10 a_feed_50 a_feed_250
villagersRecruitedw_recruit_2 a_first_villager
villagerLevelUpsd_levelup_1 w_levelup_5 a_levelup_* 3단
resourcesSoldd_sell_120 w_sell_800 m_sell_5000 a_sell_* 2단

세이브에서 파생하는 지표 12종. 카운터가 아니므로 내려갈 수 있고, 그래도 되는 것은 claimed가 한 번 켜지면 꺼지지 않기 때문입니다:

파생 지표읽는 곳쓰는 미션
인구villagers.lengtha_pop_5 a_pop_12 a_pop_20
보유 직업 수villagers[].role 집합a_all_roles
최고 주민 레벨villagers[].level 최댓값a_level_10 a_level_25
인장 영입 주민 수rosterId-5/-6으로 끝남a_premium_1 a_premium_5
가방 총량inventorya_bag_500
명성kingdom.prestiged_prestige_20 w_prestige_300 m_prestige_1200 a_prestige_* 3단
건물 인수 수kingdom.builta_build_5 a_build_15
가구 종수kingdom.furnished.lengtha_furnish_6 a_furnish_12
축제 기록 수kingdom.festivals.lengtha_festival_5 a_festival_20
가축 수kingdom.pens[].counta_livestock_10
도달 스테이지battle.highestCleared + 1a_frontier_100
착용 장비 수 / 각인 합villagers[].equipment · .enchanta_equip_20 a_enchant_10
연속·누적 접속login.streak · login.totalDaysa_streak_7 a_streak_30 a_login_100

세 가지 판단이 여기 들어 있습니다.


3. 분류와 사다리

MissionCategory 6종. 업적을 묶는 축이면서 모든 목록의 정렬 기준입니다.

분류무엇업적 수
growth 성장영입·인구·레벨12
industry 생산채집·제작·요리11
battle 전투스테이지·던전·장비7
kingdom 왕국명성·건물·가구·축제·가축10
commerce 교역판매·수입5
devotion 헌신접속을 이어 오는 것3

정렬 순서: 수령 가능 → 진행 중 → 수령 완료, 그 안에서 분류 → 사다리 → 목표치. 사다리는 id의 끝 숫자를 떼어 얻습니다(a_gather_500000a_gather). 목표치만으로 정렬하면 자원 500,000이 제작 2,000과 스테이지 500 사이에 끼어 48줄이 서로 무관한 숫자로 읽힙니다. 테스트가 한 사다리의 칸들이 붙어 있음을 강제합니다.

업적 탭은 리셋 카운트다운이 없으므로 그 자리에 달성률(mission.ach_progress, 예: 업적 12/48)을 씁니다. 수령 완료 수를 셉니다 — 받기 버튼이 살아 있는 줄은 아직 끝난 것이 아니고, 표시가 그 아래 목록과 어긋나면 안 됩니다.


4. 리셋

기간 키: KST 기준(KST_OFFSET_MS = 9h). daily=YYYY-MM-DD, weekly=그 주 월요일, monthly=YYYY-MM. 롤오버는 미션 화면을 열 때 관측합니다("observed, not scheduled"). 부팅 시에는 부르지 않습니다.

주간 키는 날짜 문자열입니다. 월요일에는 주간 키와 일일 키가 글자까지 같습니다(2026-08-03). 그래서 d_all_clearrow.period만 믿지 않고 행을 카탈로그와 맞춰 봅니다. 안 그러면 월요일에 수령한 주간 미션이 일일 완료로 세어져 보너스가 공짜로 나갑니다. 테스트가 이 날짜를 짚고 있습니다.


5. 수령

claimMission(id, now): 롤오버 먼저 → claimable 재확인 → 골드·인장 지급과 claimed:true 한 번에 기록. 자정 너머 화면에서 누른 버튼은 롤오버에 걸려 거부됩니다.

5.1 일일 미션 전체 완료 보너스

d_all_clear. 목표는 DAILY_CORE.length에서 파생합니다 — 여기에 8을 적어 두면 아홉 번째 일일이 생기는 날 조용히 일찍 지급되고, 그 방향의 오류는 돈이 나갑니다.

완료가 아니라 수령을 셉니다. 보너스는 화면에 다시 와서 비우게 만드는 장치이고, 받지 않은 일에도 지급하면 받기 버튼이 선택 사항이라고 가르치게 됩니다. 자기 행은 세지 않으므로 스스로 목표를 채울 수 없습니다.

하루치 골드의 3분의 1을 차지합니다. 쉬운 다섯 줄과 축제·대장간·상점을 한 번씩 다녀와야 하는 세 줄이 하루 일과이고, 보너스는 그 어색한 세 줄을 끝낼 이유입니다.


6. 보상표

6.1 일일 미션 (9종)

id분류목표골드인장
d_gather_150생산150400
d_craft_3생산3500
d_feed_1성장1350
d_levelup_1성장1450
d_stage_2전투2600
d_prestige_20왕국20550
d_collect_gold_200교역200300
d_sell_120교역120450
d_all_clear헌신81800
합계540/일0

6.2 주간 미션 (8종)

id분류목표골드인장
w_recruit_2성장21502
w_levelup_5성장52002
w_feed_10성장103000
w_gather_2000생산2,0002502
w_craft_20생산202002
w_stage_15전투153003
w_prestige_300왕국3004500
w_sell_800교역8003500
합계2,200/주11/주

6.3 월간 미션 (6종)

id분류목표골드인장
m_gather_12000생산12,0001,2006
m_craft_80생산801,0006
m_stage_60전투601,5008
m_prestige_1200왕국1,2001,8000
m_gold_5000교역5,00005
m_sell_5000교역5,0001,5000
합계7,000/월25/월

m_gold_5000은 자기 참조 방지를 위해 골드 보상 0.

6.4 업적 (48종)

업적은 게임에서 인장을 덩어리로 내놓는 유일한 자리이므로, 목록 전체를 인장이 쓰이는 곳 — 프리미엄 명부 — 에 대고 재 봅니다. 규칙은 6분의 1입니다.

인장
프리미엄 명부(22명)401
업적 전체 예산 상한(1/6)66
실제 업적 총액59 (14.7%)

업적을 전부 완주해도 프리미엄 명부의 6분의 1이 안 되고, 나머지 6분의 5는 인장소의 몫입니다. 양쪽 숫자를 코드에서 파생하므로 명부에 한 명을 더하면 예산도 함께 늘어납니다.

이 규칙이 표의 모양을 두 가지로 결정합니다.

  1. 인장은 사다리의 첫 칸이 아니라 마지막 칸에 붙습니다. 채집 10,000과 제작 100은 각각 한 주치 플레이인데 인장 4·5개를 냈습니다. 그러면 그 위의 모든 칸은 격하로 읽히지 않기 위해 더 많이 내야 하고, 그렇게 목록이 인장 109개(명부의 27%)까지 갔습니다.
  2. 옮긴 칸은 값을 잃지 않았습니다. 떼어낸 인장은 전부 SEAL_TO_GOLD_RATE(300) — 인장소 교환 창구의 게임 자체 환율 — 로 골드로 바꿔 넣었습니다. 1.0.0에서 나간 업적 12종은 골드 환산 값이 정확히 같습니다(테스트가 확인).

성장 12종

id지표목표골드인장
a_first_villagerrecruited1500
a_pop_5인구51,0000
a_pop_12인구122,1000
a_pop_20인구201,5004
a_all_roles직업 수RECRUITABLE_ROLES.length(현재 11)1,3005
a_level_10최고 레벨105000
a_level_25최고 레벨252,0004
a_levelup_25levelups251,9000
a_levelup_100levelups1002,4000
a_levelup_300levelups3003,6004
a_premium_1인장 영입15000
a_premium_5인장 영입52,5000
소계19,35017

a_all_roles의 목표는 코드에서 파생합니다. 예전에 7로 적혀 있었고, 왕국 콘텐츠가 직업 네 개를 더한 순간부터 사냥꾼·약초꾼·건축가·가구 장인이 없는 왕국에도 "모든 직업"을 지급하고 있었습니다.

생산 11종

id지표목표골드인장
a_gather_1000gathered1,0001500
a_gather_10000gathered10,0001,5000
a_gather_100000gathered100,0003,2000
a_gather_500000gathered500,0006,3005
a_craft_25crafted251500
a_craft_100crafted1001,9000
a_craft_500crafted5002,6000
a_craft_2000crafted2,0004,6004
a_feed_50foodEaten506000
a_feed_250foodEaten2502,2000
a_bag_500가방 수5002000
소계23,4009

전투 7종

id지표목표골드인장
a_stage_10cleared102000
a_stage_50cleared502,3000
a_stage_200cleared2002,8000
a_stage_500cleared5004,3005
a_frontier_100도달 스테이지1001,2000
a_equip_20착용 장비209000
a_enchant_10각인 합101,2000
소계12,9005

왕국 10종

id지표목표골드인장
a_prestige_500명성5005000
a_prestige_2540명성KINGDOM_PRESTIGE[8](Lv.9 = 2,540)2,4000
a_kingdom_max명성곡선 마지막 칸(현재 6,700)4,3005
a_build_5건물 인수53000
a_build_15건물 인수151,2000
a_furnish_6가구 종수64000
a_furnish_12가구 종수121,2000
a_festival_5축제 기록55000
a_festival_20축제 기록201,5003
a_livestock_10가축 수106000
소계12,9008

명성 목표 두 개는 data/kingdom.tsKINGDOM_PRESTIGE 곡선에서 읽습니다. 손으로 적은 둥근 숫자가 아니라 실제 레벨 문턱이므로, 곡선을 고치면 업적도 함께 움직입니다.

교역 5종

id지표목표골드인장
a_sell_1000sold1,0003000
a_sell_10000sold10,0001,5000
a_gold_5000earned5,00004
a_gold_50000earned50,0001,5004
a_gold_500000earned500,0005,0005
소계8,30013

a_gold_5000만 골드 0입니다. 5,000 목표에 2,000골드를 주면 미션이 스스로를 절반쯤 지불하는 셈이지만, 50,000·500,000까지 올라가면 1,500골드는 목표의 3%·0.3%여서 플레이어가 알아챌 자기 참조가 아닙니다. 사다리 전체를 인장 전용으로 묶어 둔 것이 이 사다리를 칸당 가장 비싼 사다리로 만들고 있었습니다.

헌신 3종

id지표목표골드인장
a_streak_7연속 접속73000
a_streak_30연속 접속302,1003
a_login_100누적 접속1003,3004
소계5,7007

업적 합계: 골드 82,550, 인장 59. 1.0.0(12종, 골드 3,450 · 인장 41) 대비 1회성 인장 +18.

한 사다리 안에서 뒤 칸이 앞 칸보다 값이 낮아지는 일은 없습니다 — 골드 환산으로 비교하며, 테스트가 사다리마다 확인합니다.


7. 28일 접속 캘린더

위치는 연속(streak)이 아니라 누적 수령 일수. loginCycleDay = (totalDays % 28) + 1. 하루를 빠뜨려도 바퀴는 이어집니다(streak만 1로 리셋).

골드인장골드인장
1500152000
2800162400
312001701
401182800
51800193200
62200203600
71502212503
81500223000
92000233400
10012402
112800254000
123200264500
134000275000
145004287006

한 바퀴: 골드 6,990, 인장 20. 인장 나오는 날: 4·7·10·14·17·21·24·28.


7.1 행운의 돌림판

캘린더 아래 버튼 하나. 누르면 팝업이 열리고 하루 한 번 돌립니다. 연속 보상도, 바퀴도, 밀린 회차도 없습니다 — 빠뜨린 날은 그냥 돌리지 않은 한 번입니다.

자정은 하나뿐입니다. 돌림판은 자기 날짜 키를 갖지 않고 미션과 같은 periodKey("daily", now)를 씁니다. 캘린더 버튼과 돌림판 버튼이 다른 순간에 켜지는 일이 있어서는 안 되고, 날짜 계산이 두 곳에 있으면 한쪽을 "고치는" 순간 갈라집니다.

정하는 쪽과 보여 주는 쪽이 갈라져 있습니다. 칸을 뽑고 골드·인장을 지급하고 날짜를 찍는 일은 스토어가 한 번의 쓰기로 처리하고(spinDailyWheel), 화면은 이미 정해진 칸으로 원판을 돌려 결과를 알리기만 합니다. 그래서 애니메이션은 버려도 되는 것입니다 — 돌아가는 중에 팝업을 닫거나 화면이 꺼져도 보상은 이미 들어와 있고, 다시 열면 그 칸을 가리키고 있습니다. 흔한 실수는 원판이 멈출 때 상품을 정하는 것인데, 그러면 모든 중단이 보상 손실이거나 공짜 재추첨이 됩니다.

먼저 지급한 값은 연출 쪽에서 치릅니다. 이 게임의 다른 모든 수도꼭지는 세이브가 바뀌는 순간을 그대로 축하합니다 — 획득 연출(fx/gold-gain.ts)이 스토어를 지켜보고, 지갑 숫자가 굴러 오릅니다. 돌림판은 버튼을 누른 순간 이미 지급됐으니 그 기본값을 그대로 두면 원판이 아직 세 바퀴 남았는데 화면 위로 코인이 쏟아지고 "+300"이 뜹니다. 답을 먼저 말해 버리는 것이고, 그러면 애니메이션은 이미 아는 답을 확인하는 절차로 내려앉습니다. 그래서 돌림판은 스토어에 쓰기 전에 축하를 붙잡고(fx/gain-hold.ts) 칸이 발표될 때 놓습니다. 지급은 탭한 순간, 축하는 공개하는 순간. 붙잡기가 지급을 삼킬 수는 없습니다 — 돌아가는 중에 팝업을 닫으면 그 자리에서 놓고, 붙잡기 자체도 스스로 만료됩니다. 최악의 경우는 늦게 오는 코인이지 오지 않는 코인이 아닙니다.

붙잡기는 돌림판 전용이 아닙니다. 먼저 지급하고 나중에 보여 주는 화면은 모두 같은 문제를 갖고, 지금은 왕국 행사가 두 번째 사용자입니다 — 확인을 누르면 지급되고, 시트가 닫히고, 광장이 먼저 잔치를 벌입니다. 잔치가 끝나면 보상 팝업이 내역을 읊고, 그 팝업까지 닫은 뒤에야 보류한 지갑 숫자와 코인을 공개합니다. 기본값대로면 코인이 잔치나 팝업 위로 쏟아져서 공개 순서를 앞질러 버립니다. 다만 놓는 시점이 다릅니다. 돌림판은 원판이 스스로 도니 자기 시계로 끝을 알지만, 행사는 잔치 뒤에도 플레이어가 팝업을 언제 닫을지를 기다려야 해서 기본 만료 시한 대신 자기 시한을 넘깁니다(FESTIVAL_GAIN_HOLD_MS). 새 화면을 붙일 때 물어야 할 것은 하나입니다 — 이 화면은 지급한 뒤에 보여 줄 것이 남아 있는가. 그렇다면 붙잡고, 보여 준 뒤에 놓습니다.

확률을 공개합니다. 12칸의 가중치는 합 1,000이라 확률이 표에서 그대로 읽히고, 팝업이 칸마다 백분율을 띄웁니다. 코드 모양 속에만 있는 확률은 조정도 검증도 공시도 할 수 없습니다.

보상은 인장이 주역, 골드는 곁들이입니다. 1회 기대값 약 61골드 · 0.29인장 (캘린더 하루 약 250골드 · 0.7인장). 골드를 작게 둔 이유는 인색해서가 아닙니다. 무료 고정 수입(미션+캘린더)이 이미 주머니 한 번 수거액과 몇 퍼센트 차이라서, 캘린더만 한 골드를 더 얹으면 고정 수입이 수거를 넘어서고 채집 루프 — 실제 게임 — 가 플레이어 수입의 작은 쪽이 됩니다. 인장은 그 문제가 없습니다. 인장을 채집하는 수단이 없으니 경쟁할 루프가 없습니다.

골드인장확률
gold-3030020.0%
gold-4040018.0%
gold-6060015.0%
gold-9090012.0%
gold-12012009.0%
seal-1019.0%
gold-15015005.5%
seal-2024.5%
gold-25025002.5%
seal-3032.0%
mixed-70-17012.0%
jackpot30050.5%

꽝 칸은 두지 않았습니다. 공짜 하루 1회에서 빈 칸은 "이 기능은 열 가치가 없다"를 가르치는 탭입니다.

세이브의 wheel선택 필드이고 없으면 "한 번도 돌리지 않음"으로 읽힙니다. 뜻이 정해져 있으니 SAVE_VERSION을 올리지 않아도 되고, 돌림판 이전에 쓰인 세이브는 진행도를 잃지 않고 그날의 1회를 그대로 받습니다.


8. 인장 희소성 정책

일일 미션은 골드만 줍니다(테스트 강제). 인장은 주간·월간·업적과 접속 캘린더, 그리고 행운의 돌림판만 내놓습니다. 돌림판은 확정 지급이 아니라 1회 기대값(약 0.29개/일, 월 8.6개)으로 계산됩니다 — 가중 추첨이라 월 환산이 기대값일 수밖에 없습니다. 구매한 골드·자원은 stats에 들어가지 않습니다. 자세한 정책은 두 재화에 있습니다.

출처인장주기
주간 미션11
월간 미션25
28일 캘린더2028일
돌림판 (1회 기대값)0.29
업적591회성

8.1 반복 공급 — 월 약 95개, 그대로

목록을 8종·6종으로 넓히면서도 이 숫자는 그대로입니다. 새 주간·월간 미션(w_feed_10 w_prestige_300 w_sell_800 m_prestige_1200 m_sell_5000)은 전부 골드만 냅니다. 프리미엄 명부의 가격표가 월 95개를 전제로 계산되어 있어서, 주간 미션 하나가 인장을 1개 더 내놓으면 명부 전체의 값이 바뀝니다. 반대로 경제 균형이 짚은 진짜 병목은 골드(12명 Lv.15 = 247,800)이므로, 넓힌 만큼의 보상은 골드로 냅니다. tests/missions.test.ts가 주간 11 · 월간 25 · 캘린더 20을 못으로 박아 둡니다.

8.2 1회성 공급 — 명부의 6분의 1

인장
프리미엄 명부(22명, VILLAGER_ROSTER)401
예산 상한 = 명부 ÷ 666
업적 48종 총액59 (14.7%)

업적은 인장을 덩어리로 내놓는 유일한 자리이므로 한 방향으로만 재면 됩니다: 완주해도 명부의 6분의 1을 넘지 않는다. 양쪽을 코드에서 파생하므로 명부에 프리미엄 주민을 더하면 예산도 늘고, 빼면 줄어들면서 테스트가 걸립니다.

1.0.0의 41개에서 59개로 +18. 12종에서 48종으로 4배가 되었는데 인장은 44%만 늘어난 것은 인장을 사다리의 마지막 칸으로 몰았기 때문입니다. 채집 10,000·제작 100·스테이지 50 같은 한 주치 칸이 인장을 내던 것이 그 위 모든 칸의 값을 밀어 올리고 있었습니다(한때 109개, 명부의 27%).

떼어낸 인장은 SEAL_TO_GOLD_RATE(300, 인장소 교환 창구의 게임 자체 환율)로 골드로 환산해 되돌려 놓았습니다. 그래서 1.0.0에서 나간 업적 12종은 골드 환산 값이 정확히 같고, 어떤 사다리에서도 뒤 칸이 앞 칸보다 싸지지 않습니다. 두 성질 모두 테스트가 확인합니다.

참고 — 이 문서와 경제 균형이 오래 인용해 온 "고급 주민 14명 240개"는 낡은 숫자입니다. 왕국 콘텐츠가 직업 네 개(사냥꾼·약초꾼·건축가·가구 장인)를 더하면서 프리미엄 후보가 22명 401개가 되었습니다. 예산을 비율로 적어 둔 것은 이런 낡음을 다시 만들지 않기 위해서입니다.


9. 세이브와 마이그레이션

v11 → v12 도입. missions를 미리 채우지 않고 첫 syncMissions가 baseline을 잡습니다. 옛 퀘스트 보드는 삭제됩니다.

카탈로그가 넓어져도 스키마는 그대로입니다. 새 미션은 새 행이 하나 생기는 것이고, 롤오버가 다음 화면 열기에서 채웁니다. rolledOverMissions는 이 빌드가 정의하지 않는 행을 버리므로, 미션을 지우는 것도 마이그레이션이 아닙니다. 새 지표는 전부 이미 있는 상태에서 파생하므로 옛 세이브에도 소급 적용되고, 필드가 없는 세이브에서도 0으로 읽힙니다(테스트가 kingdom·battle·equipment·enchant를 지운 세이브로 71종 전부를 확인합니다).


이어서 읽기

이 문서를 가리키는 곳