feat: Ref 호환 접속 제한과 벌점 초기화 구현

실행 중인 프로필에서만 접속 벌점을 누적하고 제한 임계값과 대상 경로를 Ref 순서에 맞춘다. 자기 턴 명령 성공 시 순간 점수를 같은 flush에서 초기화하며 월간 누적 감쇠는 유지한다. 제한 중 메인 자동 갱신과 실시간 구독을 중지하고 수동 갱신 성공 시 복구한다.
This commit is contained in:
2026-08-15 18:49:41 +00:00
parent a99f8166eb
commit 48d94e5ff2
29 changed files with 982 additions and 232 deletions
+28
View File
@@ -0,0 +1,28 @@
export const ACCESS_REFRESH_LIMIT_COEFFICIENT = 10;
export type AccessLimitLevel = 0 | 1 | 2;
export const calculateAccessRefreshLimit = (tickSeconds: number): number => {
if (!Number.isFinite(tickSeconds) || tickSeconds <= 0) {
throw new RangeError('tickSeconds must be a positive finite number.');
}
const turnMinutes = tickSeconds / 60;
return Math.round(Math.pow(turnMinutes, 0.6) * 3) * ACCESS_REFRESH_LIMIT_COEFFICIENT;
};
export const resolveAccessRefreshLimit = (tickSeconds: number, storedLimit: unknown): number => {
if (typeof storedLimit === 'number' && Number.isSafeInteger(storedLimit) && storedLimit > 0) {
return storedLimit;
}
return calculateAccessRefreshLimit(tickSeconds);
};
export const resolveAccessLimitLevel = (refreshScore: number, refreshLimit: number): AccessLimitLevel => {
if (refreshScore > refreshLimit) {
return 2;
}
if (refreshScore > refreshLimit * 0.9) {
return 1;
}
return 0;
};
+1
View File
@@ -23,3 +23,4 @@ export * from './ranking/legacyColor.js';
export * from './auth/accountIconProjection.js';
export * from './logging/formatLegacyLogHtml.js';
export * from './gateway/profileStatus.js';
export * from './game/accessPenalty.js';
@@ -0,0 +1,31 @@
import { describe, expect, it } from 'vitest';
import {
calculateAccessRefreshLimit,
resolveAccessLimitLevel,
resolveAccessRefreshLimit,
} from '../src/game/accessPenalty.js';
describe('legacy access penalty', () => {
it.each([
[60, 30],
[300, 80],
[600, 120],
[1_200, 180],
])('calculates the Ref refresh limit for %i-second turns', (tickSeconds, expected) => {
expect(calculateAccessRefreshLimit(tickSeconds)).toBe(expected);
});
it('keeps a persisted positive limit and derives a missing one', () => {
expect(resolveAccessRefreshLimit(600, 777)).toBe(777);
expect(resolveAccessRefreshLimit(600, undefined)).toBe(120);
expect(resolveAccessRefreshLimit(600, 0)).toBe(120);
});
it('uses the strict Ref threshold and warns only above ninety percent', () => {
expect(resolveAccessLimitLevel(108, 120)).toBe(0);
expect(resolveAccessLimitLevel(109, 120)).toBe(1);
expect(resolveAccessLimitLevel(120, 120)).toBe(1);
expect(resolveAccessLimitLevel(121, 120)).toBe(2);
});
});