game.js)를 직접 파서 이미 있던 더 심각한 남용 경로까지 찾아 정리한 것.
undo()가 columns/stockDeals/score/moves/completedSets만 복원하고 골드(points)는 복원하지 않는다 — 세트완성·포인트카드처럼 이미 출시된 즉발보상도 "실행→되돌리기→같은 수 다시 두기"로 무한 복제가 가능한 구조다(§1).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로 안 돌아가게. "지금 상태가 조건과 일치하는가"가 아니라 "이 게임에서 이 조건을 처음 만족한 순간인가"만 확인하면 두 유형 다 막힌다.
사용자 피드백("같은 캐릭터 3장은 명분이 있잖아")을 원칙으로 일반화 — 좋은 명분의 공통점 3가지:
| # | 후보 | 명분 | 남용위험(원안전장치 적용 후) | 예상 빈도/판 | 구현난이도 | 판정 |
|---|---|---|---|---|---|---|
| 1 | 칸 비우기(그 칸을 이 판에서 처음 비움) | 강함 빈 칸=만능 임시자리, 솔리테어 표준 전략 개념 | 낮음 | 2~5회 | 쉬움 | 채택 |
| 2 | 3장+ 뭉치 이동 | 강함 미리 순서를 맞춰뒀다는 스킬 | 높음 — 두 칸이 서로 조건을 맞추면 반복 왕복 가능(사용자 지적) | 많음 | 쉬움 | 기각(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 예방책으로만 언급) |
| 13 | K를 빈 칸으로 옮겨 새 줄 시작 | 강함 인정된 전략 오프닝 무브 | 중간 — #1(칸 비우기)과 사실상 같은 이벤트의 반대쪽이라 중복 | 1~3회 | 보통 | 기각(#1과 통합, 별도 불필요) |
| 14 | 모든 골드카드(포인트카드) 조기 수집(순서 무관 완주) | 보통 | 낮음 | 0~1회 | 쉬움 | 보류(포인트카드 자체가 이미 즉발보상 있어 중복 체감) |
| 15 | 조커 하나로 2세트 연쇄 완성(체인) | 강함(연쇄 반응은 직관적) | 낮음 | 0~1회, 매우 드묾 | 어려움 | 보류(빈도가 너무 낮아 체감 밀도 기여 적음) |
let 전역이되 snapshot() 대상에 포함되지 않음 → 되돌리기가 판을 되감아도 "이미 줬는지" 기록은 그대로 남아 재지급을 막는다. 골드 크기는 빈도가 잦을수록 소액, 드물수록 고액 원칙으로 배분(§1 A유형 잔여 위험까지 고려해 총량을 보수적으로 유지).emptiedCols (Set, 10칸 인덱스) 신설 · move()에서 src.length === 0 && !emptiedCols.has(fromCol)일 때만 지급 후 emptiedCols.add(fromCol). 같은 칸을 몇 번 다시 비워도 두 번째부터는 무시.
bestRunSeen (숫자, 초기 0) 신설 · 매 이동 후 보드 전체에서 "현재 가장 긴 연속런 길이"를 계산해 bestRunSeen보다 크면 그 차이만큼(또는 완만한 계수로) 지급 후 갱신. 줄어들었다 다시 그 길이에 도달해도 bestRunSeen 이하라 재지급 없음 — 셔플 방어 자동 내장.
stockExhaustedRewarded (불리언) 신설 · 스톡 딜 처리 지점에서 stockDeals.length === 0 && !stockExhaustedRewarded일 때 지급. 되돌리기로 스톡을 복원해도 플래그는 그대로라 재지급 없음.
allRevealedRewarded(불리언) 신설 · 매 뒤집기 이벤트 후 columns.every(col => col.every(c => c.up)) 검사(카드 이동이 있을 때만 계산해 성능 부담 최소화). 참이 되는 순간 1회 지급.
movesSinceUndo(숫자) 신설 · 매 성공한 이동마다 +1, undo() 호출 시 0으로 리셋. 20의 배수에 도달할 때마다(예: 20, 40, 60...) 지급. 되돌리기를 쓰는 순간 스트릭이 끊기므로 "되돌리기 복제"(§1 A유형)를 쓰는 플레이어는 이 보상을 아예 못 받는 구조 — 다른 보상들의 잔여 위험을 상쇄하는 효과도 있음.
snapshot()에 points를 포함시켜 되돌리기가 골드도 같이 되돌리게 함 — 근본적이지만 "골드를 도로 뺏는" UX가 어색할 수 있음(플레이어가 이미 받은 걸로 인지한 뒤 사라지는 느낌).kUndoBtn/이력 구조가 있어 같은 방식(§1)이 적용되는지 별도 확인 필요.game.js의 move() 주변에 안전 플래그 변수만 추가하는 수준이라 개발비 최소. §5(기존 시스템 되돌리기 취약점)는 이번 범위와 분리해 별도 지시 시 진행.