SSOLY TOWN · SPIDER SOLITAIRE · IN-ROUND REWARD DESIGN

판 내(in-round) 즉발 보상 이벤트 브레인스토밍

"명분 있는"(무작위 아닌, 눈에 보이는 성과 기반) 트리거 후보 전수조사 + 남용(exploit) 위험 평가 · 2026-07-15
발단: CR(Crossy Road) 대비 판당 보상 리듬이 성긴 이유가 "판 하나 길이 자체"(스파이더 평균 10분 vs CR 목숨 1개 초 단위)에 있다고 확인 → 채널을 더 쪼개는 대신 판 내부에서 자주 터지는 즉발 보상을 늘리는 방향으로 선회. 1차 시도(카드뒤집기 22% 무작위 반짝임)는 "그냥 랜덤이라 명분이 없다"는 지적으로 반려, 2차 시도(뭉치이동+칸비우기)는 "뭉치이동은 남용 우려, 칸비우기는 개수를 늘려서 촘촘하게"라는 피드백을 받음 — 이 보고서는 그 피드백을 계기로 후보군을 전수 브레인스토밍하고, 코드베이스(game.js)를 직접 파서 이미 있던 더 심각한 남용 경로까지 찾아 정리한 것.

Executive Summary

① 왜 지금까지의 시도가 흔들렸나 — 근본 원인은 "되돌리기"

확인된 사실(game.js 실측):
function snapshot() { history.push(JSON.stringify({ columns, stockDeals, score, moves, completedSets })); }
function undo() { ... columns = s.columns; stockDeals = s.stockDeals; score = s.score; moves = s.moves; completedSets = s.completedSets; }

스냅샷에 points(골드)·gameEarned·completedPiles·setBonusHistory가 전부 빠져있다. 즉 카드 배치는 되돌아가지만 이미 받은 골드는 그대로 남는다.

남용 경로 두 갈래

유형메커니즘심각도영향 범위
A. 되돌리기 복제
(진짜 무한)
골드 주는 수를 둠 → 되돌리기 → 완전히 같은 수를 다시 둠 → 골드 재지급. 판의 카드 배치가 100% 원상복구되므로 몇 번이든 반복 가능.치명적세트완성 즉발골드(+1)·포인트카드(+1)·겹침/연속 보너스 — 전부 move()/removeSetData() 안에서 snapshot() 이후에 골드를 주기 때문에 이미 출시된 기능도 이론상 노출돼 있음. 오늘 새로 만든 어떤 즉발보상이든 이 패턴을 그대로 밟으면 똑같이 뚫림.
B. 판 셔플 반복
(유한하지만 의도 밖)
"지금 이 상태인가?"만 확인하는 조건(예: 칸이 비어있는가)은 되돌리기 없이도 카드를 이리저리 옮기며 같은 상태를 여러 번 재현할 수 있음. 카드를 실제로 소모해야 하므로 무한은 아니지만, 원래 의도(그 칸을 "처음" 비웠을 때만 보상)보다 훨씬 많이 받아갈 수 있음.중간2차 시도의 "칸 비우기 +5"가 정확히 이 유형 — 이미 빈 칸에 카드 하나를 넣었다 빼는 것만으로 재발동 가능했음.
공통 해법(이 보고서 전체에 적용): 모든 신규 즉발보상의 "이미 받았는지" 기록은 snapshot()이 저장하는 필드 밖의 별도 변수로 두고, 단조증가(monotonic)로만 설계한다 — 한 번 참(true)이 되거나 최고기록을 한 번 경신하면 되돌리기·재현으로도 절대 다시 false로 안 돌아가게. "지금 상태가 조건과 일치하는가"가 아니라 "이 게임에서 이 조건을 처음 만족한 순간인가"만 확인하면 두 유형 다 막힌다.

② 명분(justification) 평가 기준

사용자 피드백("같은 캐릭터 3장은 명분이 있잖아")을 원칙으로 일반화 — 좋은 명분의 공통점 3가지:

눈에 보임
화면에서 원인을 바로 알 수 있나
스킬/의도 연관
운이 아니라 플레이어가 만든 결과인가
기존 전략 개념
솔리테어 플레이어가 이미 아는 개념인가
되돌리기 내성
§1의 안전장치를 자연스럽게 적용 가능한가

③ 전수 브레인스토밍 — 15개 후보

#후보명분남용위험(원안전장치 적용 후)예상 빈도/판구현난이도판정
1칸 비우기(그 칸을 이 판에서 처음 비움)강함 빈 칸=만능 임시자리, 솔리테어 표준 전략 개념낮음2~5회쉬움채택
23장+ 뭉치 이동강함 미리 순서를 맞춰뒀다는 스킬높음 — 두 칸이 서로 조건을 맞추면 반복 왕복 가능(사용자 지적)많음쉬움기각(2차 시도, 반려)
3카드 뒤집기 시 확률 보너스없음 순수 난수낮음(확률형 자체는 안전)많음쉬움기각(1차 시도, 반려)
4스톡 완전 소진(마지막 5번째 딜 사용)강함 "더 이상 새 카드 없음"이라는 명확한 자원 소모 마일스톤낮음0~1회(도달 못 하는 판도 있음)쉬움채택
5전체 카드 공개(뒤집힌 카드 0장)매우 강함 솔리테어 플레이어가 보편적으로 아는 "다 열렸다" 순간낮음0~1회(늦게 도달)보통 — 매 이동마다 전수 스캔 필요채택
6최장 연속런 신기록(판 내 최고 길이 갱신마다)강함 "지금까지 중 제일 긴 줄"이라는 눈에 보이는 성장낮음(신기록만 인정)3~6회(가장 잦음)보통채택
7세트완성 직전(12장째) 알림 보너스약함 사실상 세트완성 보상 선지급 — 명분이 아니라 순서 문제낮음세트당 1회쉬움기각(기존 세트보상과 중복 성격)
8조커 없이 세트완성보통 — 대부분의 세트완성이 원래 조커 없이 일어나 특별하지 않음낮음세트당 거의 항상쉬움기각(너무 흔해 "성과" 느낌 약함)
9점수 구간 마일스톤약함 점수가 이동마다 감소하는 카운터라 "달성" 개념과 안 맞음중간가변쉬움기각
10제한시간 내 세트 K개(스피드 보너스)보통낮음가변어려움 — 타이머 신설 필요기각(느긋한 게임 톤과 불일치)
11되돌리기 없이 M수 연속 진행(클린 플레이 스트릭)강함 "실수 없이 진행 중"이라는 스스로 확인 가능한 성과 + §1의 남용을 역이용 억제낮음 — 되돌리기를 쓰는 순간 스트릭이 끊겨 자기방어적1~3회보통채택
12같은 두 칸 사이 반복 이동 감지 후 억제해당 없음(방어 로직이지 보상 아님)해당 없음-보통참고용(§5 예방책으로만 언급)
13K를 빈 칸으로 옮겨 새 줄 시작강함 인정된 전략 오프닝 무브중간 — #1(칸 비우기)과 사실상 같은 이벤트의 반대쪽이라 중복1~3회보통기각(#1과 통합, 별도 불필요)
14모든 골드카드(포인트카드) 조기 수집(순서 무관 완주)보통낮음0~1회쉬움보류(포인트카드 자체가 이미 즉발보상 있어 중복 체감)
15조커 하나로 2세트 연쇄 완성(체인)강함(연쇄 반응은 직관적)낮음0~1회, 매우 드묾어려움보류(빈도가 너무 낮아 체감 밀도 기여 적음)

④ 추천 Top 5 — 설계 상세

공통 원칙: 모든 추적 변수는 let 전역이되 snapshot() 대상에 포함되지 않음 → 되돌리기가 판을 되감아도 "이미 줬는지" 기록은 그대로 남아 재지급을 막는다. 골드 크기는 빈도가 잦을수록 소액, 드물수록 고액 원칙으로 배분(§1 A유형 잔여 위험까지 고려해 총량을 보수적으로 유지).
1
칸 비우기 — 칸당 1회, +2골드
기존 시도의 수정판 — 예상 빈도 가장 안정적(판마다 2~5회)

emptiedCols (Set, 10칸 인덱스) 신설 · move()에서 src.length === 0 && !emptiedCols.has(fromCol)일 때만 지급 후 emptiedCols.add(fromCol). 같은 칸을 몇 번 다시 비워도 두 번째부터는 무시.

2
최장 연속런 신기록 — 갱신마다 (길이-3)골드
가장 자주 터지는 후보 — 카드 정렬이 진행될수록 계속 갱신됨

bestRunSeen (숫자, 초기 0) 신설 · 매 이동 후 보드 전체에서 "현재 가장 긴 연속런 길이"를 계산해 bestRunSeen보다 크면 그 차이만큼(또는 완만한 계수로) 지급 후 갱신. 줄어들었다 다시 그 길이에 도달해도 bestRunSeen 이하라 재지급 없음 — 셔플 방어 자동 내장.

3
스톡 완전 소진 — 1회, +4골드
자원 관리형 마일스톤 — 판당 최대 1회, "이제 배수진"이라는 긴장감과 자연스럽게 연결

stockExhaustedRewarded (불리언) 신설 · 스톡 딜 처리 지점에서 stockDeals.length === 0 && !stockExhaustedRewarded일 때 지급. 되돌리기로 스톡을 복원해도 플래그는 그대로라 재지급 없음.

4
전체 카드 공개 — 1회, +6골드
가장 늦게, 가장 드물게 오지만 가장 극적인 순간 — 큰 액수로 임팩트

allRevealedRewarded(불리언) 신설 · 매 뒤집기 이벤트 후 columns.every(col => col.every(c => c.up)) 검사(카드 이동이 있을 때만 계산해 성능 부담 최소화). 참이 되는 순간 1회 지급.

5
클린 플레이 스트릭 — 되돌리기 없이 20수마다 +2골드
유일하게 "행동 자체"가 아니라 "행동의 부재(되돌리기 안 씀)"에 붙는 보너스 — §1 남용 억제와 방향이 같아 이중 효과

movesSinceUndo(숫자) 신설 · 매 성공한 이동마다 +1, undo() 호출 시 0으로 리셋. 20의 배수에 도달할 때마다(예: 20, 40, 60...) 지급. 되돌리기를 쓰는 순간 스트릭이 끊기므로 "되돌리기 복제"(§1 A유형)를 쓰는 플레이어는 이 보상을 아예 못 받는 구조 — 다른 보상들의 잔여 위험을 상쇄하는 효과도 있음.

합산 체감: 평균적인 완주 판 기준 대략 칸비우기(2~5회×2g)+연속런(3~6회×1~3g)+스톡소진(0~1회×4g)+전체공개(0~1회×6g)+클린스트릭(0~2회×2g) ≈ 판당 15~35골드 내외가 새로 추가됨. 기존 세트완성(8회×1g)+포인트카드(6회×1g)=14g와 합치면 판당 즉발보상이 기존 대비 대략 2~3배 촘촘해짐 — 실제 체감은 배포 후 실측 필요(§6).

⑤ 부수 권고 — 기존 시스템도 같이 고칠지는 별도 결정 필요

§1에서 찾은 "되돌리기 복제"(A유형)는 이미 라이브에 나가 있는 세트완성·포인트카드·겹침/연속 보너스에도 이론상 적용됨. 고치는 방법은 두 가지 중 택일: 이 보고서는 문제만 특정하고 수정 여부·방식은 결정하지 않음 — 실제 악용 사례가 보고되기 전까지는 우선순위 낮은 기술부채로 남겨두는 것도 합리적 선택.

⑥ 리스크·전제의 한계

⑦ 결론 — 다음 액션

Top 5(§4) 구현 승인 시 이 세션에서 바로 착수 가능 — 전부 game.jsmove() 주변에 안전 플래그 변수만 추가하는 수준이라 개발비 최소. §5(기존 시스템 되돌리기 취약점)는 이번 범위와 분리해 별도 지시 시 진행.
Ssoly Town 내부 리서치 · 2026-07-15 · game.js 코드 직접 분석 + 브레인스토밍 15개 후보 종합