Playball Logo

Command Palette

Search for a command to run...

목차 열기

04. 위험 점수 계산 (Guard)

Guard 단계는 두 가지 입력을 받아 위험 점수를 계산·누적하고 Tier를 갱신합니다.

입력

입력출처특성
마우스 행동 feature 5가지클라이언트 telemetry필수 — 항상 수집
외부 인간 검증 점수Turnstile 같은 제3자 서비스선택 — 없으면 fail-open score 사용

5가지 행동 Feature

Feature 정의와 정규화

Feature의미정규화 범위낮을수록 위험한 이유
tremorStdDev손떨림 표준편차0 ~ 6자동화 봇은 떨림이 없어 기준선에 정확히 맞음
linearityRatio직선거리/총거리0 ~ 11에 가까울수록 시작점↔끝점 직선 = 기계적
avgVelocity평균 속도 (px/s)0 ~ 4000인간 50~1500 범위, 초고속은 비정상
dwellTime클릭 전 머뭇거림 (ms)0 ~ 2000사람은 hesitation 있음, 봇은 거의 0
pathRatio총거리/직선거리1 ~ 3≤ 1.10 + linearity ≥ 0.92면 linear_path 플래그

Raw Telemetry → Feature 계산

클라이언트가 전송한 이동·클릭 이벤트를 처리합니다.

단계계산
좌표 정규화xNorm/yNorm 0~1 범위 → 1000px virtual canvas
포인트 정렬tsMs 기준 오름차순
totalDist인접 포인트 간 유클리드 거리 합
linearDist첫 포인트와 마지막 포인트 직선 거리
duration마지막 ts - 첫 ts
linearityRatiolinearDist / totalDist
avgVelocitytotalDist / durationSec
pathRatiototalDist / linearDist
dwellTime인접 세그먼트 이동거리 ≤ 3px 또는 속도 ≤ 30px/s 구간 dtMs 누적
tremorStdDev첫-끝점 선분에 대한 중간 포인트들의 수직거리 표준편차

제약linearDist ≤ 20px이면 tremor 샘플 생성 안 함. 포인트 수 < 2면 대부분 feature 0.

내부 점수 계산

수식

s_내부 = 0.35 · tremor_bot
       + 0.25 · linearity_bot
       + 0.15 · velocity_bot
       + 0.15 · dwell_bot
       + 0.10 · linear_path

각 "bot 값" 계산

변수수식
tremor_bot1 − normalize(tremor, 0, 6)
linearity_botnormalize(linearity, 0, 1) (선형성 자체)
velocity_botnormalize(velocity, 50, 1500) (50 이하 0, 1500 이상 1)
dwell_bot1 − normalize(dwell, 0, 2000)
linear_pathlinearity > 0.92 AND pathRatio < 1.10 → 1, else 0

핵심 대칭 — 떨림과 머뭇거림은 "낮을수록 봇", 직선성과 속도는 "높을수록 봇".

가중치 설계 의도

지표가중치의미
손떨림0.35 (최대)사람의 자연스러운 떨림이 가장 흉내내기 어려움
직선성0.25두 번째로 중요
속도0.15중간 비중
머뭇거림0.15속도와 동등 비중
완벽한 직선 플래그0.10극단적 직선+고속 조합 잡는 보조 신호

외부 점수 결합

수식

s_외부봇도 = 1 − 외부점수
s_현재    = 0.30 · s_외부봇도 + 0.70 · s_내부

왜 내부 신호를 더 크게 보나 (0.70 vs 0.30)

이유설명
외부 점수의 불안정성선택적·타임아웃 가능성 있어서 주 신호로 쓰기 어려움
내부 신호의 항상성마우스 feature는 항상 수집 가능 (telemetry만 오면)
보조 검증 용도외부 점수는 "또 하나의 증거"로 보정 역할

외부 점수 누락 시

상황처리
Turnstile 토큰 없음fail-open score (기본 0.50) 사용
Turnstile 검증 실패·타임아웃fail-open score
점수 반환그 값을 external_score로 사용
점수 없이 성공1.0으로 간주

EWMA 누적

수식

r_새 = (1 − α) · r_이전 + α · s_현재     (α = 0.30)
  • 최신 관찰값을 30%만 반영, 과거 70% 유지

왜 EWMA인가

방식문제
단순 평균단일 good event로 급락 → tier 진동
이동 평균윈도우 밖 데이터 완전 잊음, 보수적 누적 어려움
EWMA최신 반영 + 과거 유지의 균형, 상승·하강 모두 부드럽게

감쇠 메커니즘

EWMA 누적 전에 r_이전에 두 종류 감쇠가 적용됩니다.

비활성 감쇠 (Passive Decay)

조건
임계 비활성 시간30초
감쇠 주기5초
감쇠 배율0.98

계산

if (now - lastGuardTs) > passive_decay_after_inactive_s:
    elapsed_ticks = (now - lastGuardTs - threshold) / 5초
    r_이전 = r_이전 × (0.98 ^ elapsed_ticks)

효과

경과 시간배율
5초0.98
1분0.98^6 ≈ 0.886
5분0.98^58 ≈ 0.308
1시간≈ 0

의도 — 1시간 뒤 돌아온 사용자는 이전 위험 누적이 거의 초기화. 공정성 유지.

챌린지 통과 감쇠 (Verification Decay)

조건
S3 챌린지 PASS 직후r_새 ← r_새 × 0.5

왜 0으로 리셋하지 않나 — 한 번의 챌린지 통과만으로 진짜 정상 사용자라고 단정 짓기 어려움. 봇이 챌린지만 뚫는 경우도 있음.

Tier 결정

EWMA 결과 r_새를 등급으로 변환합니다. 자세한 내용은 05-tier-action 참조.

Tier점수 범위
T0< 0.20
T1< 0.50
T2< 0.80
T3≥ 0.80

감사 이벤트

DEF_GUARD_SCORED 이벤트로 기록되는 정보:

필드의미
sInt내부 점수 s_내부
sExt외부 봇도 s_외부봇도
sT최종 스텝 점수 s_현재
rPrev감쇠 적용 후 이전 점수
rNew갱신된 누적 점수
missingFlags누락된 feature 목록
tier결정된 등급

VQA Telemetry 판정 (추가 지표)

VQA 챌린지의 경우 기본 botRisk 외에 boosted_risk를 추가로 계산해 max를 취합니다. 자세한 내용은 07-vqa-gate 참조.

참조