컴퓨터 방 자동 정리 + 게스트 계정 삭제(로그아웃·하루 미접속) + 테스트 10분→1분

- 방 화면을 벗어나면(홈·규칙) detach: 자리·접속 상태는 유지, 방 접속 수에서 빠짐
- 컴퓨터가 있는 방: 사람이 모두 [방 나가기]하면 바로 닫고, 아무도 안 보면 대기실·끝난 방 1분, 게임 중 30분 뒤 정리
- 게스트 로그아웃 = 계정 삭제(자리·전적·세션·소켓), 사이트를 닫고 24시간 지난 게스트도 1분 주기로 삭제
- users.last_seen_at을 요청·소켓 종료 때 갱신(이전엔 갱신 안 됨), 003 마이그레이션에서 기존 게스트 시계 초기화
- 테스트: 무작위 대국 시드를 playoutSeeds()로 평소 1/10, test:full은 전부. bun test --parallel=4, 테스트 서버 종료 대기 1s→50ms

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
EJClaw
2026-10-07 17:10:52 +09:00
parent 373231b553
commit 39223bacdd
49 changed files with 629 additions and 155 deletions

View File

@@ -1,6 +1,6 @@
/** Random playouts for 스플렌더 (docs/09 §7): 1,000 seeds plus a few full engine randomPlayout runs. */
/** Random playouts for 스플렌더 (docs/09 §7): 1,000 seeds (FULL_TESTS; 100 otherwise) plus a few full engine randomPlayout runs. */
import { describe, expect, test } from 'bun:test';
import { SeededRng, randomPlayout, seedN, type PlayerId } from '@bg/engine';
import { SeededRng, playoutSeeds, randomPlayout, seedN, type PlayerId } from '@bg/engine';
import { COLORS, NOBLES, TIERS, componentCounts, splendor, type SplendorAction, type SplendorOptions, type SplendorState, type Tokens } from './index';
const CONFIGS: Partial<SplendorOptions>[] = [
@@ -109,10 +109,11 @@ describe('random playouts (docs/09 §7)', () => {
}
}, 60_000);
test('1,000 seeded playouts: conservation, no leaks in views or events, onTimeout always legal, every game ends', () => {
const SEEDS = playoutSeeds(1000);
test(`${SEEDS} seeded playouts: conservation, no leaks in views or events, onTimeout always legal, every game ends`, () => {
const ends: Record<string, number> = {};
let maxSteps = 0;
for (let seed = 1; seed <= 1000; seed++) {
for (let seed = 1; seed <= SEEDS; seed++) {
const { state, steps } = run(seed);
maxSteps = Math.max(maxSteps, steps);
const res = splendor.result(state)!;
@@ -128,8 +129,9 @@ describe('random playouts (docs/09 §7)', () => {
if (!state.conceded.length) expect(turns.size).toBe(1);
}
}
expect(ends.target ?? 0).toBeGreaterThan(900);
expect(ends.resign ?? 0).toBeGreaterThan(0);
expect(ends.target ?? 0).toBeGreaterThan(SEEDS * 0.9);
// The first 2-player concede that ends the game is seed 195, so this needs the full run.
if (SEEDS === 1000) expect(ends.resign ?? 0).toBeGreaterThan(0);
expect(maxSteps).toBeLessThan(2000);
}, 60_000);
});