pattern: subagent-code-parse-no-inject

source: subagent

adapter: rhythm-battle

error_message: 서브에이전트가 URLSearchParams 파싱 코드를 삽입했으나 실제로 게임 객체에 스탯을 주입하지 않음 (2회 반복)

resolution: 서브에이전트에게 파일당 개별 발주. 대형 파일(2000줄+)에서 정확한 삽입 지점 찾기는 Sonnet 약점. 파싱 코드 삽입 후 반드시 실제 게임 객체 주입 검증 필요.

trigger_condition: 서브에이전트에게 대형 파일(1500줄+)에서 특정 위치에 코드 삽입 요청 시, 특히 게임 로직 흐름을 추적해야 하는 경우

resolution_pattern: 1) 파일당 개별 발주로 분할 2) 삽입 후 실제 효과 검증 단계 추가 3) 대형 파일은 Opus 모델 고려 4) 'parse + apply' 두 단계를 명시적으로 분리 지시

applicable_when:
서브에이전트가 1000줄 이상 파일 수정
게임 로직/상태 머신 같은 복잡한 흐름 추적 필요
파싱과 적용이 분리된 로직

not_applicable_when:
단순 텍스트 치환
독립적인 함수 추가
200줄 미만 파일

status: active

notes: 밸런스 테스트 v2에서 2회 반복 확인. 서브에이전트가 '수정했다'고 보고했으나 실제로는 파싱만 하고 게임 객체 미주입.


pattern: iframe-postmessage-test-env-broken

adapter: multi-adapter-scenario

error_message: postMessage 기반 iframe 통신이 Playwright standalone 모드에서 수신 불가 (81건 타임아웃)

resolution: BE 매개 통신(REST API)로 전환. iframe/standalone/Playwright 모두 지원하려면 HTTP가 유일한 범용 해법.

trigger_condition: iframe 내부 게임이 postMessage로 부모에게 결과 전송하는 구조에서, Playwright가 standalone URL로 직접 접근 시

resolution_pattern: 1) postMessage 대신 REST API 엔드포인트 사용 2) 테스트는 API GET으로 결과 수집 3) iframe 모드는 postMessage 병행 가능 (하위호환)

applicable_when:
iframe 기반 게임/컴포넌트 테스트
E2E 테스트가 standalone 모드 필요
postMessage 수신자가 테스트 환경에서 없음

not_applicable_when:
단일 페이지 애플리케이션
부모 프레임이 항상 존재하는 경우
실시간 양방향 통신 필요 (WebSocket 대안)

notes: 밸런스 테스트 v1 81건 전부 타임아웃. v4에서 REST API로 전환 후 해결. rhythm-battle/realtime-rpg 수신 확인.


error_message: file:// 프로토콜로 어댑터 HTML 열면 /shared/ 리소스(battle-shim.js 등) 로드 실패 → 게임 미초기화

resolution: E2E에서 file:// 대신 http://localhost:{port}/adapters/{id}/ URL 사용. 서버 serving 필요.

trigger_condition: Playwright E2E에서 file://${path}/index.html로 어댑터 열고, HTML 내부에 <script src='/shared/...'> 절대경로 리소스 참조 시

resolution_pattern: 1) E2E baseURL을 http://localhost:{port}로 설정 2) 서버 없이 테스트 불가능한 구조는 사전에 simple_server.py 실행 3) 또는 리소스를 인라인 삽입 (비권장)

applicable_when:
절대경로 리소스 참조 (/shared/, /api/)
E2E 테스트가 file:// 프로토콜 사용
브라우저 CORS 제약으로 로컬 파일 접근 불가

not_applicable_when:
모든 리소스가 상대경로 또는 인라인
단일 HTML 파일로 완결
CDN 리소스만 사용

notes: hints E2E 초기 4/6 실패. file:// → http:// 변경 후 6/6 PASS. 실제 어댑터 버그 아님 (false negative).


pattern: subagent-self-declaration-unreliable

adapter: card-battle

task_label: card-battle-impl

error_message: 서브에이전트가 '10/10 PASS, 콘솔 에러 0' 보고했으나 실제로는 3턴 플레이만 검증. 메뉴↔전투 화면 겹침 버그 미발견.

resolution: 발주 시 완료 조건을 구체적으로 명시 ('승리 화면 도달' 등). 별도 E2E 검증 에이전트 투입. 자기선언 신뢰 금지.

trigger_condition: 서브에이전트가 구현 + 자체 테스트를 동시에 수행하고 '완료' 보고 시

resolution_pattern: 1) 구현 ≠ 검증 분리 (200줄+ 필수) 2) 발주 프롬프트에 명확한 완료 조건 (예: '엔딩 도달', 'E2E Happy Path PASS') 3) 별도 검증 에이전트 투입 (읽기 전용) 4) 자동화된 E2E 테스트 우선

applicable_when:
구현 작업이 200줄 이상
게임/UI 같은 엔드투엔드 동작 검증 필요
서브에이전트 자체 테스트 결과에 의존

not_applicable_when:
단순 유틸리티 함수
단위 테스트로 충분한 경우
150줄 미만 단순 작업

notes: card-battle/puzzle-match3 구현 시 발생. 발주 프로토콜에 '구현≠검증 분리' 규칙 추가됨.


pattern: large-code-sonnet-simplify-tendency

adapter: tactics-grid

error_message: Sonnet에게 1918줄 전투 코드 복사 요청 시 829줄로 간소화 재구현 (지시 위반)

resolution: 대용량 코드 정밀 복사/추출은 Opus 사용. Sonnet은 토큰 제약으로 자체 판단 간소화 경향. 대안: cp + sed/patch 패턴으로 발주.

trigger_condition: Sonnet에게 1000줄+ 코드를 '원본 그대로 복사' 또는 '정밀 추출' 요청 시

resolution_pattern: 1) Opus 모델 사용 (정밀 추출 입증됨) 2) 또는 서브에이전트에게 쉘 명령(cp, sed, patch) 기반 발주 3) 복사 후 diff 검증 단계 추가

applicable_when:
1000줄 이상 코드 복사/추출
원본 충실성이 중요한 경우
Sonnet 모델 사용

not_applicable_when:
새 코드 생성 (복사 아님)
간소화가 목적인 경우
500줄 미만 코드

notes: Opus는 1918줄 원본 충실 추출 성공 (5분33초). Sonnet은 빠르고 효율적이나 대용량 복사에는 부적합.


pattern: ssot-violation-by-copy

source: manual

adapter: multi-adapter-demo

error_message: 전투 코드를 adapters/에 복사 → 원본과 분기 → 버그 유입 (3회 반복 실패)

resolution: 복사 금지. 시나리오가 iframe으로 canonical 경로 참조. SSOT 원칙 준수.

trigger_condition: 같은 코드를 여러 위치에 복사하여 사용하려 할 때

resolution_pattern: 1) 원본 위치를 canonical로 정의 2) 다른 곳에서는 참조(iframe, import, symlink) 3) 복사본 생성 금지 4) 기존 복사본은 삭제하고 참조로 전환

applicable_when:
코드 재사용 필요
여러 프로젝트/모듈에서 동일 코드 사용
iframe 또는 ES modules 사용 가능

not_applicable_when:
독립 실행 필요한 standalone 배포
인라인 필수 (성능/보안)
버전별 분기 필요 (의도적 fork)

notes: multi-adapter v1/v2/v3 전부 실패. SSOT 참조 방식으로 전환 후 해결. Sonnet의 대용량 코드 복사 시 간소화 경향도 복합 원인.


pattern: juice-harness-important-conflict

task_label: juice-harness-apply

error_message: Juice Harness의 .grid-tile.selected가 !important로 보라색 강제 → 이후 팀 색상 규칙(.selected.ally 파랑, .selected.enemy 빨강) 충돌

resolution: .selected.ally/.selected.enemy로 분리하여 팀 구분. !important 남용 지양.

trigger_condition: CSS !important로 스타일 강제 적용 후, 후속 작업에서 해당 요소에 다른 스타일 규칙 추가 시

resolution_pattern: 1) !important 사용 최소화 2) 우선순위는 클래스 조합으로 해결 (.selected.ally) 3) 서브에이전트에게 'CSS !important 금지' 제약 명시

applicable_when:
CSS 스타일 충돌 발생
!important 사용된 코드 수정
서브에이전트가 CSS 작성

not_applicable_when:
외부 라이브러리 오버라이드 필수
인라인 스타일 우선 필수
단일 파일, 후속 수정 없음

notes: tactics-grid에 Harness A(juice) 적용 후 팀 색상 구분 버그. !important 제거 후 해결.


pattern: exec-failure-alert-occludes-response

task_label: general

error_message: exec 실패 시 suppressToolErrors: true 설정에도 텔레그램 알림 전송 → 실제 답장 메시지 유실/가려짐

resolution: 실패 가능 명령은 || true로 감싸서 exit code 0 보장. pgrep, curl 헬스체크, kill 등 주의.

trigger_condition: exec 명령이 실패할 수 있고(pgrep 프로세스 없음, curl 타임아웃 등), suppressToolErrors 설정되어 있으나 여전히 에러 알림이 텔레그램에 전송되는 경우

resolution_pattern: 1) 실패 가능성 있는 명령은 `command || true` 패턴 사용 2) 또는 적절한 에러 핸들링 (pgrep || echo 'not found') 3) 필수 실패는 명시적 exit code로 구분

applicable_when:
pgrep, killall 같은 조건부 성공 명령
curl 헬스체크
프로세스 존재 여부 확인

not_applicable_when:
실패가 critical error인 경우
에러 알림이 필요한 경우
단위 테스트/CI 환경

notes: 텔레그램 채널에서 exec 에러 알림이 실제 응답을 가려 Partner가 놓치는 사례 반복. || true 패턴으로 해결.


pattern: grep-based-verification-false-positive

task_label: hints-review

error_message: grep 기반 검증 에이전트가 'hints 파싱 코드 없음' 보고했으나 실제로는 전부 구현되어 있음 (함수명 불일치로 검색 실패)

resolution: grep 대신 실제 실행 기반 검증. 또는 전용 검증 에이전트가 코드 전체 읽고 확인.

trigger_condition: grep/ack 같은 텍스트 검색으로 코드 존재 여부 확인 시, 함수명/패턴이 예상과 다른 경우

resolution_pattern: 1) 코드 검증은 실제 실행 또는 전용 파싱 에이전트 사용 2) grep은 초기 후보 찾기용으로만 3) 검증 에이전트는 파일 전체를 읽고 로직 추적

applicable_when:
코드 존재 여부 검증
함수/변수명이 표준화되지 않은 경우
grep 기반 자동 검증

not_applicable_when:
함수명이 명확히 정의된 경우
단순 텍스트 존재 확인
로그 파일 검색

notes: rhythm-battle hints 검증 시 false positive. 전용 검증 에이전트 재투입 후 전부 구현 확인.


pattern: server-process-signal-leak

task_label: serving

error_message: nohup으로 서버 실행 시 exec의 SIGTERM이 자식 프로세스로 전파되어 서버 종료

resolution: setsid로 프로세스 세션 격리. exec 호출 시 서버가 영향받지 않도록.

trigger_condition: nohup으로 백그라운드 서버 실행 후, exec 도구로 다른 명령 실행 시 서버가 예기치 않게 종료되는 경우

resolution_pattern: 1) setsid {command} & 로 세션 격리 2) systemd 같은 프로세스 매니저 사용 3) 또는 screen/tmux 세션 내부 실행

applicable_when:
장시간 실행 서버 프로세스
exec 도구 사용 환경
nohup/백그라운드 실행

not_applicable_when:
일회성 스크립트
systemd 환경
컨테이너 내부 (격리 보장)

notes: cloudflared + FastAPI 서버가 exec 후 종료되는 문제. setsid로 해결.


pattern: gateway-crash-subagent-announce-loss

task_label: gateway-crash

error_message: 게이트웨이 크래시(Node.js TLS 버그) 시 서브에이전트 announce 메시지 유실 → 세션 컨텍스트 리셋

resolution: 서브에이전트 산출물을 반드시 파일로 영속화. announce 유실 시에도 파일에서 복구 가능하도록.

trigger_condition: 게이트웨이 프로세스 크래시 또는 재시작 시, 진행 중이던 서브에이전트의 announce 메시지가 전달 전에 유실되는 경우

resolution_pattern: 1) 서브에이전트 결과를 파일로 기록 (orchestration/results/{label}-{ts}.json 등) 2) HEARTBEAT.md에 '미수신 서브에이전트 확인' 추가 3) 게이트웨이 재시작 후 파일 기반 복구 로직

applicable_when:
장시간 실행 서브에이전트
게이트웨이 불안정 환경
중요 산출물 유실 위험

not_applicable_when:
짧은 작업 (5분 이내)
산출물이 중요하지 않은 경우
안정적인 인프라

notes: 2026-03-15 게이트웨이 크래시로 서브에이전트 announce 유실. 발주 프로토콜에 '에러 JSON 파일 기록' 규칙 추가.


pattern: autoplay-bot-blamed-for-balance

task_label: stat-injection-fix

error_message: rhythm-battle autoplay 봇이 패배하는 이유를 봇 로직 버그로 의심했으나, 실제로는 기본 밸런스 문제 (baseDamage 낮음, bossHP 높음). 봇은 정상 작동 중.

resolution: 봇 코드 조사 → 정상 확인 (노트 인식/타이밍 정확) → 밸런스 수치 확인 → baseDamage/bossHP 조정으로 해결

trigger_condition: autoplay 테스트 실패 시 봇 로직을 먼저 의심하고, 실제로는 데이터/밸런스가 원인인 경우

resolution_pattern: 1) 봇 로직 간단 검증 (노트 인식, 입력 타이밍 정상?) 2) 밸런스 수치 확인 (승리 가능한 데미지인가?) 3) 봇 수정은 최후 수단 4) 데이터 먼저, 코드 나중 원칙

applicable_when:
autoplay 봇 존재
테스트 실패
로직/데이터 경계가 불명확
밸런스 조정 가능한 게임

not_applicable_when:
봇 코드에 명백한 에러 로그
봇이 아예 실행 안됨
밸런스 수정 불가능 (고정 설계)

notes: stat-injection-fix-report.md에서 발견. rhythm-battle 봇 조사 후 로직 정상 확인, 밸런스 문제로 판명. 일반적 패턴: 실패 → 코드 의심보다 데이터 먼저 확인.


pattern: standalone-mode-early-exit-no-callback

adapter: adapter-bridge

task_label: bridge-fix

error_message: adapter-bridge.js가 standalone 모드에서 if (!isIframe) return; early exit하여 onComplete 콜백 미등록 → Playwright 직접 URL 접근 시 BattleResult 미수신

resolution: standalone 모드에서도 onComplete 등록, window.__lastBattleResult 노출. early return 전에 핸들러 등록 또는 standalone 분기 추가.

trigger_condition: iframe 체크 후 early return하는 구조에서, standalone 모드 실행 시 필수 핸들러 미등록

resolution_pattern: 1) early return 전에 필수 핸들러 등록 2) 또는 standalone 모드 분기 추가 (if (!isIframe) { /* standalone 핸들러 */ return; }) 3) iframe 여부와 무관하게 핸들러는 등록, postMessage 전송만 조건부

applicable_when:
postMessage 기반 통신
iframe/standalone 양쪽 지원 필요
테스트 환경이 standalone URL 직접 접근
early return 패턴 사용

not_applicable_when:
iframe 전용 설계 (standalone 지원 불필요)
postMessage 미사용
early return 없음

status: excluded

exclude_reason: 이미 bridge-fix-report에서 수정 완료. standalone 모드 핸들러 등록 추가됨 (2026-03-15). 현재 시스템에서 더 이상 발생 안 함.

notes: bridge-fix-report.md에서 발견. 수정 완료되었으나 교훈으로 기록. 향후 유사 구조 설계 시 참고용.


pattern: adapter-integration-repetitive-manual

error_message: 7개 어댑터에 adapter-bridge.js 스크립트 태그 추가 작업 반복 (각 15-30분 × 7 = 2-4시간 소요)

resolution: 체크리스트 작성, 각 어댑터 HTML 수동 편집. 향후 공통 스크립트 자동 주입 방안 검토.

trigger_condition: 공통 모듈(bridge, shim, hints-common 등) 업데이트 시, 각 어댑터에 수동 통합 필요

resolution_pattern: 1) 공통 스크립트를 shared/ 경로에 두고 전 어댑터가 참조 (CDN 방식) 2) 또는 빌드 스크립트로 자동 주입 3) 어댑터 템플릿 HTML 생성기 (adapter-template.html) 4) 최소: 체크리스트 + 자동 검증 스크립트

applicable_when:
다수 어댑터 존재 (5개+)
공통 인프라 변경 빈도 높음
수동 통합 시간 비용 큼 (1시간+)

not_applicable_when:
어댑터 3개 이하
공통 인프라 변경 드묾 (1회성)
각 어댑터 구조가 너무 달라 자동화 불가능

notes: balance-v3-action-items.md에서 발견. 7개 어댑터 반복 작업 필요. 자동화 미구현 상태.


pattern: stat-params-ignored-hardcoded-level

adapter: multi-adapter

error_message: 전 어댑터에서 쿼리 파라미터 (pHP, pATK, eHP 등) 파싱하지만 실제 게임 로직에 미반영. enemyLevel=1 하드코딩 유지.

resolution: 각 어댑터의 CONFIG/CHARACTER_STATS 초기화 시점에 URL 파라미터 값 주입 코드 추가. 파싱 후 console.log로 적용 확인.

trigger_condition: URL 파라미터 파싱 코드 존재하나, 파싱된 값을 게임 초기화 시 실제 변수에 대입하지 않음

resolution_pattern: 1) 파싱 후 반드시 적용 검증 단계 추가 (console.log로 CONFIG/CHARACTER_STATS 확인) 2) 파싱과 적용을 한 함수에서 처리 (parseAndApplyStats) 3) E2E 테스트에서 스탯 반영 확인 (난이도별 승률 차이)

applicable_when:
다단계 데이터 흐름 (파싱 → 정제 → 적용)
서브에이전트가 파싱만 구현
적용 누락 가능성 높음

not_applicable_when:
파싱과 적용이 한 줄 (직접 대입)
단순 구조
적용 누락 불가능

notes: balance-v4-report.md에서 발견. 전 어댑터 공통 문제. 일부 수정됨 (stat-injection-fix-report), 하지만 새 어댑터에서 재발 가능.


adapter: auto-battle

error_message: Easy 난이도에서 패배, Hard 난이도에서 승리 발생 (난이도 역전 현상)

resolution: 스탯 파라미터를 게임 로직에 정상 반영 후 해결. 난이도별 결과 분포 확인 (Easy>Equal>Hard 순 승률).

trigger_condition: 난이도 파라미터 전달되나 게임 로직에 미반영, 승패가 RNG나 고정 밸런스에만 의존

resolution_pattern: 1) 난이도 파라미터를 실제 게임 변수(enemyHP, enemyATK)에 매핑 2) 난이도별 결과 분포 확인 (Easy>Equal>Hard 순 승률 예상) 3) 역전 발견 시 매핑 누락 의심 4) 자동 테스트로 난이도 곡선 검증

applicable_when:
다난이도 게임
파라미터 전달 구조 존재
자동 테스트로 역전 감지 가능

not_applicable_when:
단일 난이도
난이도가 의도적으로 역전 (hard가 쉬운 설계)
테스트 불가능

notes: balance-v4-report.md에서 발견. auto-battle에서 Easy 패배, Hard 승리. 원인: enemyLevel=1 고정 (스탯 미반영).


pattern: stat-scaling-ui-only-judge-ignored

adapter: visual-novel

task_label: hints-expansion-review

error_message: atkToPersuasion, defToResilience 등 스탯 스케일링이 UI 레이어(onChoice 함수)에만 적용, __JUDGE__.applyTurn 순수 함수는 스탯 무시 → 시뮬레이션 vs 실제 불일치

resolution: 스탯 연동을 applyTurn 순수 함수 내부로 이동. UI와 순수 함수 모두 같은 스탯 적용 로직 사용.

trigger_condition: UI 레이어와 순수 함수(__JUDGE__) 분리 구조에서, 스탯 스케일링이 UI만 적용되고 순수 함수는 고정 공식 사용

resolution_pattern: 1) 순수 함수에 스탯 파라미터 추가 (__JUDGE__.applyTurn(state, choice, stats)) 2) UI와 순수 함수 모두 같은 스탯 적용 로직 사용 3) E2E 테스트에서 시뮬레이션 vs 실제 결과 비교

applicable_when:
UI/비즈니스 로직 분리
__JUDGE__ 같은 순수 함수 존재
시뮬레이션 기능 제공 (flat 상관 검증 필요)

not_applicable_when:
UI와 로직 미분리
순수 함수 없음
시뮬레이션 불필요

notes: hints-expansion-review-2026-03-19.md에서 발견. visual-novel 스탯 연동 재작업 필요 권고됨. flat 상관 문제 (UI와 순수 함수 불일치).


error_message: 9종 어댑터 모두 atmosphere 기능 독자 구현 (~80% 코드 유사도, 총 8000줄 중복 추정)

resolution: shared/hints-common.js로 공통 모듈 추출 권장 (30% 코드 감축 가능). applyAtmosphere, checkTurnLimit 등 공통 함수 제공.

trigger_condition: 여러 어댑터에서 동일 기능(atmosphere, turnLimit 체크 등) 구현 시, 각각 독자 구현 (코드 유사도 70%+)

resolution_pattern: 1) 공통 함수를 shared/ 모듈로 추출 (applyAtmosphere, checkTurnLimit, parseCommonHints) 2) 각 어댑터는 import하여 사용 3) 중복 코드 제거, 단일 소스 유지

applicable_when:
3개 이상 어댑터가 같은 기능 구현
코드 유사도 높음 (70%+)
공통 모듈 추출 가능

not_applicable_when:
어댑터마다 구현 완전히 다름
공통 추출 불가능 (UI 구조 차이 등)
중복 코드 양 적음 (1000줄 미만)

notes: hints-expansion-review-2026-03-19.md에서 발견. atmosphere 구현 3가지 방식 존재하지만 모두 유사. shared 모듈 미구현 상태.


error_message: hints 변형 E2E 테스트 부재 → 회귀 테스트 불가, hints 변경 시 영향 미확인

resolution: 각 어댑터마다 최소 3종 hints 변형 테스트 케이스 작성 (정상/경계/비정상), __JUDGE__.simulate() 기반 자동 검증, CI 통합.

trigger_condition: 새 기능 추가 (hints, 스탯 등) 후, E2E 테스트 작성 없이 완료 선언

resolution_pattern: 1) 기능별 E2E 테스트 최소 3건 작성 (정상/경계/비정상) 2) CI에서 자동 실행 3) 스크린샷 비교 또는 시뮬레이션 결과 비교 4) 회귀 테스트 자동화

applicable_when:
자동 테스트 가능한 기능
회귀 리스크 높음
E2E 인프라 존재 (Playwright 등)

not_applicable_when:
수동 테스트만 가능
일회성 기능
회귀 리스크 낮음

notes: hints-expansion-review-2026-03-19.md에서 발견. E2E 테스트 증거 부재, 자동 회귀 테스트 인프라 미구축.


pattern: project-naming-collision-context-bleed

task_label: workspace-reset

error_message: OrbisMachinae/OrbisNext/OrbisClean 3종 프로젝트가 Orbis* 공통 접두사로 인해 도구/스크립트가 잘못된 경로 호출, SSOT 혼란, 문서 교차 오염 발생 → 워크스페이스 리셋 필요

resolution: 프로젝트별 고유 이름 사용. 같은 도메인이어도 명확히 구분 가능한 이름 (예: orbis-prod, orbis-reboot, orbis-canon). 워크스페이스당 단일 활성 프로젝트 집중.

trigger_condition: 여러 프로젝트가 공통 접두사 또는 유사 이름 사용 시, 도구/스크립트가 경로/파일을 잘못 참조

resolution_pattern: 1) 프로젝트명에 역할 구분 접미사 추가 (-prod, -dev, -canon) 2) 워크스페이스당 1개 활성 프로젝트 원칙 3) 프로젝트 전환 시 명시적 컨텍스트 리셋 문서 작성 4) 스크립트에 프로젝트명 하드코딩 금지, 환경변수 사용

applicable_when:
3개 이상 관련 프로젝트 병존
공통 도메인/테마 공유
스크립트/도구가 경로 참조
SSOT 문서 존재

not_applicable_when:
완전히 독립적인 프로젝트
프로젝트가 2개 이하
명확히 구분되는 이름

notes: exploration-old-workspace.md에서 확인. OrbisMachinae/OrbisNext/OrbisClean 3종이 3주간 혼재하며 문서 sprawl + 도구 혼란 → 최종 workspace-260312 리셋으로 해결.


task_label: orbisnext-abandonment

error_message: OrbisNext 그린필드 재작성 (4일 투자, 17,591줄 prose) → Partner는 OrbisMachinae 개선 원했으나 전면 재작성 진행 → 최종 폐기

resolution: 그린필드 재작성은 명시적 마일스톤별 승인 필수. '개선'과 '재작성'은 다른 작업. Partner 의도 확인 후 착수.

trigger_condition: 'improve X' 요청을 'rebuild X from scratch'로 확대 해석, 사전 승인 없이 대규모 재작성 착수

resolution_pattern: 1) 재작성 vs 개선 범위를 명시적으로 확인 2) 그린필드는 Phase 0 (기획) → Partner 승인 → Phase 1 진행 3) '개선' 요청은 기본적으로 incremental 해석 4) 대규모 변경은 POC 먼저 → 승인 후 본작업

applicable_when:
Partner가 '개선' 또는 '수정' 요청
기존 코드베이스 존재
재작성 범위 불명확

not_applicable_when:
명시적 '새로 만들기' 지시
기존 코드 없음 (신규 프로젝트)
POC/실험 단계

notes: exploration-old-workspace.md Section 3.2. OrbisNext 4일 (Mar 6-9) 후 폐기. Partner는 OrbisMachinae 개선 의도였으나 전면 재작성 진행 → Sunk cost fallacy.


pattern: infra-before-requirements-stable

task_label: lora-training

error_message: LoRA 캐릭터 학습 인프라 구축 (RunPod/TensorArt/ComfyUI) 시작 → 캐릭터 디자인 미확정 → 학습 데이터셋 재작업 반복 → 최종 미완성

resolution: 인프라 작업은 요구사항(캐릭터 디자인) 확정 후 시작. 불확실한 단계에서 인프라 투자 금지.

trigger_condition: 요구사항(디자인, 스펙)이 유동적인 상태에서 인프라(학습, 서버, CI/CD) 구축 착수

resolution_pattern: 1) 요구사항 freeze 확인 후 인프라 작업 2) 불확실 시 로컬 POC로 검증 먼저 3) 인프라는 안정적 요구사항에만 투자 4) 디자인 변경 → 인프라 작업 일시중단

applicable_when:
디자인/스펙 유동적
인프라 구축 비용 높음 (GPU, 서버)
재작업 리스크 존재

not_applicable_when:
요구사항 확정됨
POC 단계 (빠른 실험 필요)
인프라 비용 낮음

notes: exploration-old-workspace.md Section 3.3. LoRA 학습 인프라 구축했으나 캐릭터 디자인 계속 변경 → 데이터셋 재작업 → 미완성. 인프라 vs 요구사항 타이밍 역전 문제.


pattern: cron-jobs-not-lifecycle-managed

task_label: cron-cleanup

error_message: Cron job 15개 정의 → 프로젝트 전환 (OrbisNext 폐기) 후에도 10개 미정리 (deprecated 경로 참조) → Noise 유발

resolution: Cron job은 프로젝트 라이프사이클과 연동. 프로젝트 종료/전환 시 관련 job 즉시 비활성화. 정기 리뷰(월 1회) 필수.

trigger_condition: 프로젝트 전환/종료 후 해당 프로젝트 cron job이 계속 실행되어 실패 알림 발생 또는 잘못된 경로 참조

resolution_pattern: 1) Cron job 생성 시 프로젝트명 태그 추가 2) 프로젝트 종료 시 해당 태그 job 전체 비활성화 3) 월 1회 cron job 리뷰 (disabled job 삭제) 4) Job 설명에 프로젝트 lifecycle 명시

applicable_when:
다수 cron job 존재 (5개+)
프로젝트 전환/종료 빈번
자동화 작업 많음

not_applicable_when:
Cron job 3개 이하
단일 프로젝트만 존재
전환/종료 없음

notes: exploration-old-workspace.md Section 3.4. 15개 중 10개 disabled 상태 방치. OrbisMachinae 터널/우선순위 job이 프로젝트 전환 후에도 유지 → 에러 발생.


pattern: deliverable-format-misalignment

task_label: battle-specs

error_message: Battle design 작업 → 10개 markdown 스펙 문서 산출 (roadmap, matrix) → Partner는 플레이 가능한 프로토타입 기대 → Mismatch

resolution: 작업 시작 전 산출물 형식 명시적 확인 (문서 vs 프로토타입 vs 프로덕션). '디자인' 요청도 프로토타입 기대 가능.

trigger_condition: Partner가 '만들어줘' 또는 '디자인' 요청 시, Agent는 문서 작성, Partner는 동작하는 코드 기대

resolution_pattern: 1) 작업 시작 전: '산출물 형식: 문서/프로토타입/프로덕션?' 명시적 질문 2) 불명확 시 기본값 = 프로토타입 (동작 우선) 3) 문서만 필요하면 명시적 지시 기대 4) 'Battle design' 같은 모호한 요청은 프로토타입 우선

applicable_when:
요청이 모호 ('만들기', '디자인')
문서 vs 코드 선택 가능
Partner 기대 불명확

not_applicable_when:
명시적 '문서 작성' 지시
명시적 '코드 구현' 지시
이전 유사 작업 패턴 명확

notes: exploration-old-workspace.md Section 3.5. Battle Trinity+Plus 스펙 10개 문서 산출 → 코드 0줄. AutoGen demo는 실제 게임 4개 생성. 산출물 형식 사전 정렬 필요.


task_label: synopsis-ssot

error_message: SSOT 문서(SYNOPSIS_SINGLE_SOURCE.md)에 커밋 해시, 확인 날짜, 변경 이력 포함 → Agent가 '과거 결정'과 '현재 사실' 혼동

resolution: SSOT 문서는 현재 상태만 기록. 이력은 git log 또는 별도 CHANGELOG. 확인 날짜/커밋 해시 제거.

trigger_condition: SSOT 문서에 '2026-03-XX 확정', 'commit abc123에서 변경' 같은 historical metadata 포함

resolution_pattern: 1) SSOT는 timeless 현재 상태만 (날짜, 커밋 언급 금지) 2) 이력은 git log 활용 3) 변경 사유는 별도 DECISIONS.md 또는 git commit message 4) 정기 리뷰로 historical metadata 제거

applicable_when:
SSOT 문서 존재
문서가 자주 업데이트됨
여러 Agent가 문서 참조

not_applicable_when:
CHANGELOG (이력이 목적)
일회성 문서
git 미사용

notes: exploration-old-workspace.md Section 2.4. Partner 명시: 'SSOT에 changelog, commit hash 포함 금지'. historical metadata가 Agent의 '현재 진실' 인식을 방해.


pattern: playwright-core-missing-binaries

error_message: playwright-core 패키지 사용 → 'Chromium binary not found' 에러 (WSL headless 환경)

resolution: playwright 패키지 사용 (playwright-core 아님). playwright는 브라우저 바이너리 포함, playwright-core는 미포함.

trigger_condition: playwright-core 패키지로 브라우저 실행 시도 시, headless 환경에서 브라우저 바이너리 없음 에러

resolution_pattern: 1) package.json에 'playwright' 사용 (not playwright-core) 2) 또는 별도 npx playwright install chromium 실행 3) 서브에이전트 발주 시 명시: 'require("playwright") 사용할 것'

applicable_when:
WSL/headless/Docker 환경
Playwright 사용
브라우저 바이너리 없음

not_applicable_when:
로컬 GUI 환경 (브라우저 설치됨)
playwright 패키지 이미 설치
외부 브라우저 경로 지정

notes: env-constraints.md 명시. playwright-core는 API만 제공, 바이너리 미포함. WSL headless에서는 playwright 필수.


pattern: wsl-headless-font-missing

task_label: screenshot-korean

error_message: WSL headless Chromium에서 한글 텍스트 렌더링 시 폰트 fallback 실패 → 깨진 문자 또는 빈 박스

resolution: HTML <head>에 Google Fonts CDN으로 'Noto Sans KR' 명시적 로드. 시스템 폰트 의존 금지.

trigger_condition: WSL/headless 환경에서 한글 폰트 미설치 상태로 Chromium 실행, 한글 텍스트 렌더링

resolution_pattern: 1) HTML <head>에 Google Fonts CDN 추가 (<link href='https://fonts.googleapis.com/css2?family=Noto+Sans+KR...'>) 2) CSS font-family에 'Noto Sans KR' 명시 3) 서브에이전트 발주 시 템플릿 제공

applicable_when:
WSL/headless/Docker 환경
한글 텍스트 렌더링
스크린샷/PDF 생성

not_applicable_when:
로컬 GUI 환경 (폰트 설치됨)
영문 전용
폰트 이미 임베딩

notes: env-constraints.md 명시. WSL headless Chromium은 시스템 폰트 없음. CDN으로 해결.


pattern: sequential-ui-automation-timeout

error_message: 게임 대화 스킵을 순차 클릭으로 구현 → 15초+ 소요, RAM 3.8GB 환경에서 타임아웃

resolution: page.evaluate()로 게임 상태 직접 조작 (대화 skip, 전투 노드 점프). 순차 클릭 금지.

trigger_condition: E2E 테스트에서 UI 이벤트 순차 클릭으로 게임 진행 시도, 리소스 제약 환경에서 타임아웃

resolution_pattern: 1) page.evaluate()로 DOM/게임 객체 직접 조작 2) 예: scenarioEngine.loadNode('BATTLE_001') 3) 순차 클릭은 최후 수단 (실제 유저 플로우 검증 시만) 4) 서브에이전트에게 게임 객체 API 제공

applicable_when:
게임/복잡한 UI 자동화
시간 제약 (테스트 타임아웃)
리소스 제약 환경 (RAM 제한)

not_applicable_when:
단순 페이지 (몇 번 클릭)
실제 유저 플로우 검증 필수
게임 객체 API 없음

notes: env-constraints.md 명시. 대화 스킵 순차 클릭 15초+, RAM 3.8GB 제약. page.evaluate()로 직접 노드 점프가 해법.


pattern: api-field-case-mismatch

task_label: db-session-update

error_message: API에서 camelCase(chaptersCompleted, battlesPlayed)로 전송하지만 db.py allowed_fields는 snake_case만 인식 → 필드 저장 실패

resolution: db.py에 camelCase→snake_case 매핑 dict 추가. API와 DB 간 명명 규칙 불일치 해결.

trigger_condition: API와 DB 레이어가 다른 명명 규칙 사용 시 (camelCase vs snake_case), 필드명 검증 로직이 케이스 변환 없이 직접 비교

resolution_pattern: 1) 레이어 경계에 명명 규칙 변환 매핑 추가 (camelCase_to_snake_case dict) 2) ORM/스키마 레벨에서 자동 변환 (SQLAlchemy의 name 옵션) 3) API 문서화에 케이스 규칙 명시 4) 테스트에 케이스 변환 검증 추가

applicable_when: 명명 규칙이 언어별로 다름 (JS: camelCase, Python: snake_case)

applicable_when: 필드명 검증 또는 허용 목록 사용

not_applicable_when:
단일 레이어 애플리케이션
모든 레이어가 같은 명명 규칙 사용
필드명 검증 없음 (자유 필드)

notes: commit 9eb7693. 영속성 테스트 29/29 통과로 수정 확인. 일반적인 JS↔Python 통합 문제.


pattern: tdz-error-const-before-declaration

task_label: iframe-tdz-crash

error_message: const CHARACTER_STATS 선언 전에 스탯 오버라이드 블록에서 참조 → TDZ(Temporal Dead Zone) 에러로 스크립트 전체 크래시

resolution: 스탯 오버라이드 블록을 const 선언 이후로 이동. 선언 순서 정리.

trigger_condition: JS에서 const/let 변수 선언 전에 해당 변수를 참조하는 코드 실행 시 (호이스팅되지 않음)

resolution_pattern: 1) 선언 순서 정리 (선언 → 사용) 2) ESLint no-use-before-define 규칙 활성화 3) 초기화 블록을 함수로 분리하여 명시적 호출 순서 보장

applicable_when:
대형 단일 파일 스크립트 (1000줄+)
선언과 사용이 멀리 떨어진 경우
리팩토링 중 코드 블록 재배치

not_applicable_when:
함수 선언 (호이스팅됨)
모듈 import (항상 최상단)
작은 파일 (200줄 미만)

notes: commit ceb68ce. iframe 모드 크래시 → 선언 순서 수정 후 해결. TDZ는 JS 기본 특성이지만 대형 파일에서 발견 어려움.


pattern: iframe-domcontentloaded-race

task_label: iframe-init-timing

error_message: iframe 모드에서 DOMContentLoaded 이벤트 리스너 등록 전에 이미 발생 → 초기화 코드 미실행, DOM 접근 null 에러

resolution: document.readyState === 'loading' 체크 패턴으로 교체. 이미 로드 완료면 즉시 실행, 아니면 addEventListener.

trigger_condition: iframe 내부 스크립트가 DOMContentLoaded 리스너 등록 시, 부모 프레임에서 이미 로드 완료된 경우 이벤트 미발생

resolution_pattern: 1) readyState 체크 패턴 사용: if (document.readyState === 'loading') { addEventListener('DOMContentLoaded', init); } else { init(); } 2) 또는 DOMContentLoaded 대신 즉시 실행 (defer/module script) 3) DOM 접근 전 null 체크 추가

applicable_when:
iframe 환경
동적 스크립트 주입
부모 프레임에서 iframe 콘텐츠 제어

not_applicable_when:
일반 페이지 로드 (iframe 아님)
모듈 스크립트 (자동 defer)
DOM 접근 없음

notes: commit 97d2703. tactics-grid, card-battle에서 동일 패턴 발견. readyState 체크로 해결.


error_message: <script src='../shared/theme-shim.js'> 상대경로로 로드 시 어댑터 디렉토리 깊이에 따라 404 에러 (9개 어댑터 영향)

resolution: ../shared/ → /shared/ 절대경로로 변경. 서버 라우팅이 /shared/를 정적 파일 경로로 매핑.

trigger_condition: 여러 깊이의 디렉토리에서 공통 리소스를 상대경로(../)로 참조 시, 디렉토리 구조 변경 또는 서빙 경로 변경 시 깨짐

resolution_pattern: 1) 공통 리소스는 절대경로 사용 (/shared/, /assets/) 2) 서버에서 정적 파일 라우팅 설정 3) 또는 빌드 단계에서 경로 리졸브 (번들러) 4) 상대경로는 같은 디렉토리 내에서만 사용

applicable_when:
다중 깊이 디렉토리 구조
공통 리소스 공유
서버 서빙 환경

not_applicable_when:
단일 깊이 디렉토리
빌드 단계에서 경로 해결 (webpack 등)
로컬 파일 시스템 전용

notes: commit ee6722b. 9개 어댑터 동시 수정. 기존 2026-03-19-001(file:// 프로토콜)과 유사하나 다른 원인 (상대경로 vs 프로토콜).


pattern: iframe-early-return-skip-listener

adapter: tactics-grid,card-battle

task_label: battlestart-listener-missing

error_message: if (!isIframe) return; early exit 후 BattleStart 메시지 리스너 미등록 → iframe 모드에서 게임 시작 안됨

resolution: early return 전에 BattleStart 리스너 등록, 또는 standalone 모드 분기 추가하여 양쪽 모두 핸들러 등록.

trigger_condition: iframe 체크 후 early return하는 구조에서, return 이후 코드에 필수 이벤트 리스너 등록이 있는 경우

resolution_pattern: 1) early return 전에 필수 핸들러 등록 2) 또는 if (isIframe) { /* iframe 전용 */ } else { /* standalone 전용 */ } 분기로 재구성 3) 공통 핸들러는 조건문 밖에 배치

applicable_when:
iframe/standalone 양쪽 지원
postMessage 기반 통신
early return 패턴 사용

not_applicable_when:
iframe 전용 설계
early return 없음
이벤트 리스너 불필요

notes: commit 3740572, 2d3d329. tactics-grid, card-battle 동일 패턴. BattleStart 리스너 추가로 해결. 기존 2026-03-19-004(standalone-mode-early-exit)와 동일하나 excluded 상태. 이 패턴은 active로 유지 (다른 어댑터에서 재발 가능).


pattern: script-load-order-callback-undefined

adapter: realtime-rpg,card-battle

task_label: battle-shim-load-order

error_message: 게임 스크립트가 base64 스프라이트 로드 후 콜백에서 BattleShim 호출하는데, battle-shim.js가 나중에 로드되어 ReferenceError: BattleShim is not defined

resolution: battle-shim.js를 게임 스크립트보다 먼저 로드하도록 <script> 태그 순서 변경.

trigger_condition: A.js가 비동기 콜백에서 B 객체 참조, <script src='A.js'> → <script src='B.js'> 순서로 로드 시 콜백 실행 타이밍에 따라 B 미정의 가능

resolution_pattern: 1) 의존성 있는 스크립트를 먼저 로드 (B.js → A.js) 2) 또는 모듈 시스템 사용 (import) 3) 비동기 콜백에서 null 체크 또는 ready 이벤트 대기

applicable_when:
비동기 콜백에서 전역 객체 참조
<script> 태그 순차 로드
의존성 관계 명시되지 않음

not_applicable_when:
동기 실행만 (콜백 없음)
모듈 번들러 사용 (webpack 등)
defer/async 속성으로 명시적 순서 제어

notes: commit 5fed743. realtime-rpg, card-battle 로드 순서 수정. 비동기 콜백 + 스크립트 로드 순서 조합 문제.


pattern: battle-result-field-name-inconsistency

adapter: factory

task_label: victory-판별-불일치

error_message: BattleShim은 result='win'/'lose' 필드로 전송, 일부 어댑터는 victory=true/false로 전송. Factory 테스터가 victory 필드만 확인하여 항상 패배 판정.

resolution: Factory에서 result='win' 및 victory=true 양쪽 인식하도록 fallback 로직 추가. remainingHP/totalDamage도 stats.* 및 top-level 양쪽 지원.

trigger_condition: 여러 구현체가 같은 인터페이스를 다른 필드명으로 전달 시, 수신 측이 하나의 필드명만 확인

resolution_pattern: 1) 수신 측에서 여러 필드명 fallback 처리 (victory ?? (result === 'win')) 2) 인터페이스 스키마 문서화 + 검증 3) 송신 측 표준화 (모든 구현체가 같은 필드명 사용) 4) TypeScript 같은 타입 시스템으로 컴파일 타임 검증

applicable_when:
다중 구현체가 같은 인터페이스 구현
필드명 표준화 안됨
수신 측이 단일 필드명만 확인

not_applicable_when:
단일 구현체
스키마 검증 있음 (JSON Schema, TypeScript)
필드명 표준화됨

notes: commit eac1aa3. Factory 테스터 수정으로 해결. BattleResult 인터페이스 표준화 필요하나 당분간 fallback으로 대응.


pattern: hints-parsing-incomplete-missing-field

task_label: enemyAction-누락

error_message: parseAdapterHints에 enemyAction 파싱 코드 누락 → 적 방해 행동(petrify/shuffle/poison) 미작동

resolution: parseAdapterHints에 enemyAction 파싱 추가, startBattle에 기본값 초기화 추가, enemyTurn에서 gameState._enemyAction 참조로 변경.

trigger_condition: 다중 필드 파싱 함수에서 일부 필드 누락, 해당 필드 사용처에서 undefined 참조 → 기능 미작동

resolution_pattern: 1) 파싱 함수 작성 시 전체 필드 체크리스트 사용 2) 사용처에서 필드 접근 전 존재 여부 검증 3) 기본값 초기화 (defaultHints 객체) 4) E2E 테스트로 전체 필드 사용 확인

applicable_when:
다중 필드 객체 파싱
파싱 함수와 사용처가 분리됨
필드 추가/변경 빈번

not_applicable_when:
단일 필드 또는 작은 객체
파싱과 사용이 한 곳
TypeScript 타입 검증

notes: commit 1e23bf2. puzzle-match3에서 발견. hints 확장 시 파싱 누락 위험 존재. 기존 2026-03-19-006(stat-params-ignored)과 유사하나 다른 레이어 (hints vs stats).


task_label: sendPlayLog-undefined

error_message: sendPlayLog() 호출 시 adapter-bridge.js 미로드 환경에서 ReferenceError: sendPlayLog is not defined → 스크립트 크래시

resolution: sendPlayLog을 optional로 처리: typeof sendPlayLog === 'function' && sendPlayLog(...) 또는 no-op stub 정의.

trigger_condition: 선택적 외부 라이브러리/스크립트가 제공하는 함수를 무조건 호출 시, 라이브러리 미로드 환경에서 에러 발생

resolution_pattern: 1) 함수 호출 전 존재 여부 확인 (typeof fn === 'function') 2) 또는 no-op stub 정의 (const sendPlayLog = sendPlayLog || (() => {})) 3) try-catch로 감싸기 (비권장, 디버깅 어려움)

applicable_when:
선택적 의존성 (플러그인, 분석 도구 등)
standalone/통합 양쪽 지원
외부 스크립트 로드 여부 불확실

not_applicable_when:
필수 의존성
항상 로드 보장
모듈 시스템 (import 실패 시 에러)

notes: commit d17f736. 3개 어댑터 동시 수정. adapter-bridge.js 제거 시 fallback 처리 필요. BattleShim은 stub 제공.


pattern: stat-injection-overwritten-by-config

task_label: enemy-hp-injection-ignored

error_message: BattleShim.setEnemyHP(v)가 game.bossHP 설정하지만, startGame()에서 config.bossHP로 덮어씀 → 스탯 주입 무효, 항상 기본값(800) 사용

resolution: startGame() 내에서 BattleShim 주입값이 있으면 config 대신 사용, 또는 startGame 후 재주입.

trigger_condition: 초기화 함수(startGame 등)가 config 객체에서 값을 읽어 게임 상태를 덮어쓰는 구조에서, config 업데이트 전에 게임 상태만 직접 수정한 경우

resolution_pattern: 1) 초기화 함수 호출 전에 config를 먼저 업데이트 (setter가 config도 함께 수정) 2) 또는 초기화 함수 내에서 외부 주입값 우선 확인 (if (injectedHP) use injectedHP else use config) 3) 초기화 후 재주입

applicable_when:
config 기반 초기화 패턴
외부 파라미터 주입 + 기본 config 병존
초기화 함수가 config로 덮어씀

not_applicable_when:
config 미사용 (직접 초기화만)
초기화 함수 없음
외부 주입 없음

notes: adapter-game-critique.md + commit cd33e6f. rhythm-battle BattleShim setter가 config도 함께 업데이트하도록 수정됨. 하지만 일부 어댑터(rhythm-battle)에서 startGame이 config를 다시 읽어 덮어쓰는 구조 존재. 기존 2026-03-19-006(stat-params-ignored)과 유사하나 다른 레이어 (BattleShim setter vs 파싱).


adapter: realtime-rpg

task_label: dt-frame-dependent-movement

error_message: 플레이어 이동 속도가 dt(deltaTime) 그대로 곱해져서 프레임레이트 의존 → 고fps에서 빠르게, 저fps에서 느리게 이동

resolution: dt를 기준 프레임 시간(16.67ms)으로 나눈 정규화 계수 사용: dt / 16.67 → 60fps 기준으로 일관된 이동 속도.

trigger_condition: 게임 루프에서 dt(deltaTime)를 물리/이동 계산에 직접 사용 시, 프레임레이트 변동에 따라 게임 속도 변함

resolution_pattern: 1) dt를 기준 프레임 시간으로 정규화 (const dtNorm = dt / 16.67; speed * dtNorm) 2) 또는 고정 타임스텝 사용 (fixed timestep loop) 3) 프레임 독립적 물리 엔진 사용

applicable_when:
requestAnimationFrame 기반 게임 루프
dt(deltaTime) 계산 사용
물리/이동 시뮬레이션

not_applicable_when:
고정 프레임레이트 (setInterval)
턴제 게임 (시간 무관)
물리 엔진이 dt 정규화 자동 처리

notes: commit d17f736. realtime-rpg dt 정규화 추가. 일반적인 게임 프로그래밍 실수.


pattern: report-result-double-call-monkey-patch

task_label: shim-report-result-double

error_message: endGame() 본문에 BattleShim.reportResult() 직접 호출 + onGameEnd 콜백에서도 reportResult() 호출 → BattleResult postMessage/fetch 2번 발송

resolution: endGame() 본문의 reportResult() 제거하고 onGameEnd 콜백에만 의존, 또는 그 반대. 한쪽만 호출.

trigger_condition: monkey-patch로 함수 래핑 시, 원본 함수 내부와 래핑 콜백 양쪽에서 같은 사이드 이펙트 호출

resolution_pattern: 1) 원본 함수 수정 (reportResult 제거) + 콜백에만 의존 2) 또는 콜백 제거 + 원본만 유지 3) 멱등성 보장 (중복 호출 허용) 4) 호출 플래그로 중복 방지 (hasReported)

applicable_when:
함수 monkey-patch 패턴
원본 함수 수정 불가능 + 래핑 필요
사이드 이펙트 함수 (API 전송 등)

not_applicable_when:
원본 함수 수정 가능
사이드 이펙트 없음 (순수 함수)
멱등성 보장된 경우

notes: shim-review-rhythm-battle.md에서 발견. 서버 멱등성 보장 시 실질적 문제 없으나 불필요한 네트워크 요청. 수정 권장.