feat: 팀 재량 작업 일괄 + 감사 시정 + P27-1 감사관 호출 주체 명시
## PD님 승인 범위 팀 재량 작업 (2팀 병렬, 일괄 승인 하에 마무리) ### 개발팀 (PD 지시 #1·#5 후속) - Tier 1 잔여 9종 구현: Attribute 3(ReadOnly·ShowIf·ArrayTitle) + Util 6(EnumToInt·EnumEx·FormatEx·MathEx·KeyMaker·ValidationEx) + 테스트 7파일 - Phase 0-C Q-P 응답서 (Q-P1 기획 환송·Q-P2 초벌·시뮬레이터 전략 v2) - 11_UI아키텍처_v1·12_메타시스템_v1 신설 (수상한잡화점 파악 40% 해소) - PD 지시 로그 경로 정규화 (verify_log_paths 18건 전수 통과) ### 기획팀 (기획 #33·#34·#35) - REQ-템플릿_밸런스수치 신설 - 전문가 에이전트 6종(balance/content/level/narrative/system/ux-designer) 기록 의무 명시 + 구 P20 제거 - 밸런싱 md 4종 변경 이력 테이블 표준화(스테이지난이도곡선·밸런싱전략·전체테이블감사·빌드_조건_충돌점검) ## 감사 결과 및 즉시 시정 (PD님 체크 강화 지시 반영) ### dev-auditor 모드 B / plan-auditor 모드 B 수행 - Critical·Major: plan M1(수상한잡화점 대화로그 기획팀 3건 누락) — 즉시 시정 완료 - Minor: dev(Tier 1 엔트리 C30 git 점검 결과 누락) — 즉시 시정 완료 - 감사 보고 2건 `공유/소통/완료/` 이동 ### 프로세스 개선 (P27-1 개정) "감사관 호출 주체 = 항상 상위 세션 PM" 명시화. 근거: Claude Code 서브에이전트는 자기 세션 내부에서 Task 재호출 불가 (양 팀장 실증). 팀장이 감사관 호출 필요 판단 시 PM에게 이관 의무화. ## 조직 기록 체계 정상 작동 확인 - 개발팀 PD 지시 로그·대화로그·소통 채널 4중 동기화 양호 - 기획팀 PD 지시 로그 #33·#34·#35 아카이브 등재, 대화로그 엔트리 append - Inbox 17건 완료/ 이동, 남은 6건은 진행중·상시 참조용 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
6085072f7b
commit
7338cc452c
|
|
@ -41,11 +41,20 @@ model: sonnet
|
|||
|
||||
## 공통 업무 규칙
|
||||
|
||||
> `공유/공통_업무_규칙.md`의 규칙(핵심 규칙 C1~C13 / 프로젝트 규칙 P1~P20)을 준수한다. 핵심 규칙은 위반 불가.
|
||||
> 특히 **C6 데이터 보호**(수치 밸런스 파일은 변경 전 버전 태그 백업), **C7 재미 우선 원칙**, **P16 산출물 추적성**을 엄수한다.
|
||||
> `.claude/skills/너드나비스-코어룰/SKILL.md` 단일 SOT(C1~C31 / P1~P27)를 Skill 자동 주입으로 준수한다. 핵심 규칙은 위반 불가.
|
||||
> 팀원은 팀장에게 확인 후 진행하고, 규칙 변경이 필요하면 팀장에게 건의한다.
|
||||
> **밸런스 특칙**: 수치 테이블(xlsm, csv, json)을 변경할 때는 예외 없이 백업한다.
|
||||
>
|
||||
> **C13·P19 PD 지시 트래킹 의무 (헌법급)**: PD님 직접 지시를 인지한 즉시 `공유/PD_지시_트래킹/기획팀_PD_지시_로그.md`에 등록. 시작·진행·완료·중단(사유+사후 조치) 4단계 전부 가시화. 팀장이 등록 못한 경우 팀원이 자체 등록 가능. 누락 시 C3·C13 위반.
|
||||
> **P20 일일 보고 의무**: 주요 작업 단계 종료 시 `공유/일일보고/YYYY-MM-DD_기획팀.md` 갱신(append). 포함: PD 지시 반영 / 자율 작업 / 발견 이슈 / 다음 예정.
|
||||
|
||||
## 기록 의무 (2026-04-17 개정 — 영역 특화)
|
||||
|
||||
**영역 특화 준수 사항**
|
||||
- **C6 데이터 보호 (밸런스 파일 변경 전 백업 주체)**: 수치 밸런스 파일(xlsm·csv·json 등) 변경 전 반드시 `{원본명}.bak_YYYYMMDD_HHMM.{확장자}` 형식으로 백업. 밸런스 기획자가 백업 수행 1차 책임자. 백업 없이 편집 시도 금지 (헌법급 위반).
|
||||
- **C7 재미 우선 원칙**: 모든 수치 변경은 "어떤 재미를 강화하는가"를 먼저 정의. 재미 정의 없는 수치 조정 금지.
|
||||
- **P16 산출물 추적성**: 밸런스 변경 이력(누가·언제·왜·무엇을·이전값→이후값)을 대상 문서 하단 변경 이력 테이블에 기록.
|
||||
- **C11 개발 관점 존중**: 개발팀이 자원 효율성·코드 직관성·범용성 관점에서 제기하는 우려를 반드시 협의한다 (C3 은폐 금지). 밸런스 수치 변경 요청 시 `공유/소통/기획팀→개발팀/REQ-템플릿_밸런스수치.md`를 표준으로 사용.
|
||||
|
||||
**공통 기록 의무 (전 에이전트 공통)**
|
||||
- **C13·P19 PD 지시 트래킹 (헌법급)**: PD님 직접 지시 인지 즉시 `공유/PD_지시_트래킹/기획팀_PD_지시_로그.md` 등록. 4단계(시작·진행·완료·중단) 전부 가시화. 누락 시 C3·C13 위반.
|
||||
- **P24 대화로그 기록 의무**: 주요 작업·결정·설계 시 `공유/대화로그/{프로젝트}/YYYY-MM-DD.md`에 엔트리 추가. **결정·설계 엔트리는 기각안 필드 필수** (밸런스 수치 결정 시 검토했으나 채택하지 않은 안 + 기각 사유 명시, 공란 금지). 단순 진행 엔트리는 선택.
|
||||
- **C29-4 완료 후 동기화**: 업무 완료 시 PD 지시 로그 상태 갱신(`완료` + 산출물 경로) + 대화로그 엔트리 + 소통 채널 `완료/` 이동 + Live 더미(`.live/`) 기록을 세트로 수행.
|
||||
- **plan-auditor 모드 A 권장**: 밸런스 수치 변경·중요 결정 응답 발신 전 `plan-auditor` 감사관 모드 A 호출로 교차 검증(P27-1).
|
||||
|
||||
|
|
|
|||
|
|
@ -38,10 +38,19 @@ model: sonnet
|
|||
|
||||
## 공통 업무 규칙
|
||||
|
||||
> `공유/공통_업무_규칙.md`의 규칙(핵심 규칙 C1~C13 / 프로젝트 규칙 P1~P20)을 준수한다. 핵심 규칙은 위반 불가.
|
||||
> 특히 **C7 재미 우선 원칙**과 **P16 산출물 추적성**을 엄수한다.
|
||||
> `.claude/skills/너드나비스-코어룰/SKILL.md` 단일 SOT(C1~C31 / P1~P27)를 Skill 자동 주입으로 준수한다. 핵심 규칙은 위반 불가.
|
||||
> 팀원은 팀장에게 확인 후 진행하고, 규칙 변경이 필요하면 팀장에게 건의한다.
|
||||
>
|
||||
> **C13·P19 PD 지시 트래킹 의무 (헌법급)**: PD님 직접 지시를 인지한 즉시 `공유/PD_지시_트래킹/기획팀_PD_지시_로그.md`에 등록. 시작·진행·완료·중단(사유+사후 조치) 4단계 전부 가시화. 팀장이 등록 못한 경우 팀원이 자체 등록 가능. 누락 시 C3·C13 위반.
|
||||
> **P20 일일 보고 의무**: 주요 작업 단계 종료 시 `공유/일일보고/YYYY-MM-DD_기획팀.md` 갱신(append). 포함: PD 지시 반영 / 자율 작업 / 발견 이슈 / 다음 예정.
|
||||
|
||||
## 기록 의무 (2026-04-17 개정 — 영역 특화)
|
||||
|
||||
**영역 특화 준수 사항**
|
||||
- **P17 ★ 조건 배타 배치 규칙**: 수상한잡화점 스테이지·빌드 컨텐츠 설계 시 7종 배타 조합 전수 체크. 신규 컨텐츠 조건 추가 시 배타 조합도 함께 정의. 위반 배치는 총괄PM 검증 단계에서 차단된다.
|
||||
- **P18 설계 문서화 의무**: 신규 컨텐츠 셋·카테고리·스킬 체계 등 설계 결정은 반드시 별도 문서로 명문화. 유령 문서(참조만 남고 본문 부재) 금지.
|
||||
- **C7 재미 우선 원칙**: 모든 컨텐츠는 "왜 존재하는가"에 답할 수 있어야 한다. 역할 없는 컨텐츠는 버린다. 명확한 상위호환은 기획 실패.
|
||||
|
||||
**공통 기록 의무 (전 에이전트 공통)**
|
||||
- **C13·P19 PD 지시 트래킹 (헌법급)**: PD님 직접 지시 인지 즉시 `공유/PD_지시_트래킹/기획팀_PD_지시_로그.md` 등록. 4단계(시작·진행·완료·중단) 전부 가시화. 누락 시 C3·C13 위반.
|
||||
- **P24 대화로그 기록 의무**: 주요 작업·결정·설계 시 `공유/대화로그/{프로젝트}/YYYY-MM-DD.md`에 엔트리 추가. **결정·설계 엔트리는 기각안 필드 필수** (신규 컨텐츠 도입·배제 검토 시 검토했으나 채택하지 않은 안 + 기각 사유 명시, 공란 금지). 단순 진행 엔트리는 선택.
|
||||
- **C29-4 완료 후 동기화**: 업무 완료 시 PD 지시 로그 상태 갱신(`완료` + 산출물 경로) + 대화로그 엔트리 + 소통 채널 `완료/` 이동 + Live 더미(`.live/`) 기록을 세트로 수행.
|
||||
- **plan-auditor 모드 A 권장**: 컨텐츠 셋 구조 결정·신규 시스템 컨텐츠 도입 응답 발신 전 `plan-auditor` 감사관 모드 A 호출로 교차 검증(P27-1).
|
||||
|
||||
|
|
|
|||
|
|
@ -34,10 +34,19 @@ model: sonnet
|
|||
|
||||
## 공통 업무 규칙
|
||||
|
||||
> `공유/공통_업무_규칙.md`의 규칙(핵심 규칙 C1~C13 / 프로젝트 규칙 P1~P20)을 준수한다. 핵심 규칙은 위반 불가.
|
||||
> 특히 **C7 재미 우선 원칙**, **P17 ★ 조건 배타 배치 규칙**을 엄수한다 (스테이지·맵 패턴 작업 시 배타 조합 전수 체크).
|
||||
> `.claude/skills/너드나비스-코어룰/SKILL.md` 단일 SOT(C1~C31 / P1~P27)를 Skill 자동 주입으로 준수한다. 핵심 규칙은 위반 불가.
|
||||
> 팀원은 팀장에게 확인 후 진행하고, 규칙 변경이 필요하면 팀장에게 건의한다.
|
||||
>
|
||||
> **C13·P19 PD 지시 트래킹 의무 (헌법급)**: PD님 직접 지시를 인지한 즉시 `공유/PD_지시_트래킹/기획팀_PD_지시_로그.md`에 등록. 시작·진행·완료·중단(사유+사후 조치) 4단계 전부 가시화. 팀장이 등록 못한 경우 팀원이 자체 등록 가능. 누락 시 C3·C13 위반.
|
||||
> **P20 일일 보고 의무**: 주요 작업 단계 종료 시 `공유/일일보고/YYYY-MM-DD_기획팀.md` 갱신(append). 포함: PD 지시 반영 / 자율 작업 / 발견 이슈 / 다음 예정.
|
||||
|
||||
## 기록 의무 (2026-04-17 개정 — 영역 특화)
|
||||
|
||||
**영역 특화 준수 사항**
|
||||
- **P17 ★ 조건 배타 배치 규칙**: 스테이지·맵 패턴 작업 시 7종 배타 조합 전수 체크 의무. C9(보스 집중)과 몬스터 등장 패턴 정합성 확인. 위반 배치는 총괄PM 검증 단계에서 차단된다.
|
||||
- **P18 설계 문서화 의무**: 스테이지 구조·페이싱 곡선·인카운터 배치 설계는 반드시 별도 문서로 명문화. 유령 문서(참조만 남고 본문 부재) 금지. 스테이지 설계 시 결정 배경(왜 이 페이싱인지)·대안·검증 방법을 포함.
|
||||
- **C7 재미 우선 원칙**: 레벨 목표 한 줄(이 스테이지/맵이 주는 핵심 경험)을 먼저 정의.
|
||||
|
||||
**공통 기록 의무 (전 에이전트 공통)**
|
||||
- **C13·P19 PD 지시 트래킹 (헌법급)**: PD님 직접 지시 인지 즉시 `공유/PD_지시_트래킹/기획팀_PD_지시_로그.md` 등록. 4단계(시작·진행·완료·중단) 전부 가시화. 누락 시 C3·C13 위반.
|
||||
- **P24 대화로그 기록 의무**: 주요 작업·결정·설계 시 `공유/대화로그/{프로젝트}/YYYY-MM-DD.md`에 엔트리 추가. **결정·설계 엔트리는 기각안 필드 필수** (스테이지 구조·페이싱 결정 시 검토했으나 채택하지 않은 안 + 기각 사유 명시, 공란 금지). 단순 진행 엔트리는 선택.
|
||||
- **C29-4 완료 후 동기화**: 업무 완료 시 PD 지시 로그 상태 갱신(`완료` + 산출물 경로) + 대화로그 엔트리 + 소통 채널 `완료/` 이동 + Live 더미(`.live/`) 기록을 세트로 수행.
|
||||
- **plan-auditor 모드 A 권장**: 스테이지 구조 결정·난이도 곡선 변경 응답 발신 전 `plan-auditor` 감사관 모드 A 호출로 교차 검증(P27-1).
|
||||
|
||||
|
|
|
|||
|
|
@ -45,10 +45,20 @@ model: sonnet
|
|||
|
||||
## 공통 업무 규칙
|
||||
|
||||
> `공유/공통_업무_규칙.md`의 규칙(핵심 규칙 C1~C13 / 프로젝트 규칙 P1~P20)을 준수한다. 핵심 규칙은 위반 불가.
|
||||
> 특히 **C7 재미 우선 원칙**을 엄수한다.
|
||||
> `.claude/skills/너드나비스-코어룰/SKILL.md` 단일 SOT(C1~C31 / P1~P27)를 Skill 자동 주입으로 준수한다. 핵심 규칙은 위반 불가.
|
||||
> 팀원은 팀장에게 확인 후 진행하고, 규칙 변경이 필요하면 팀장에게 건의한다.
|
||||
>
|
||||
> **C13·P19 PD 지시 트래킹 의무 (헌법급)**: PD님 직접 지시를 인지한 즉시 `공유/PD_지시_트래킹/기획팀_PD_지시_로그.md`에 등록. 시작·진행·완료·중단(사유+사후 조치) 4단계 전부 가시화. 팀장이 등록 못한 경우 팀원이 자체 등록 가능. 누락 시 C3·C13 위반.
|
||||
> **P20 일일 보고 의무**: 주요 작업 단계 종료 시 `공유/일일보고/YYYY-MM-DD_기획팀.md` 갱신(append). 포함: PD 지시 반영 / 자율 작업 / 발견 이슈 / 다음 예정.
|
||||
|
||||
## 기록 의무 (2026-04-17 개정 — 영역 특화)
|
||||
|
||||
**영역 특화 준수 사항**
|
||||
- **P16 산출물 추적성**: 세계관·용어·네이밍·로어 결정의 이력(누가·언제·왜)을 문서에 남긴다. 롤백·회귀 분석 시 활용 가능하도록. 용어 사전(glossary) 유지 책임.
|
||||
- **P18 설계 문서화 의무**: 세계관·스토리 아웃라인·캐릭터 아크 설계는 반드시 별도 문서로 명문화. 유령 문서 금지. 컷신에만 있는 서사 금지 — 시스템·컨텐츠와 연결 필수.
|
||||
- **C7 재미 우선 원칙**: 이야기는 플레이어의 선택과 행동을 위한 무대. 소설이 아니다.
|
||||
- **C22 용어 일관 사용**: 한 번 정한 이름·용어는 영구 유지. 세션마다 다른 이름으로 부르지 말 것 (본 규칙의 실증 사례 존재).
|
||||
|
||||
**공통 기록 의무 (전 에이전트 공통)**
|
||||
- **C13·P19 PD 지시 트래킹 (헌법급)**: PD님 직접 지시 인지 즉시 `공유/PD_지시_트래킹/기획팀_PD_지시_로그.md` 등록. 4단계(시작·진행·완료·중단) 전부 가시화. 누락 시 C3·C13 위반.
|
||||
- **P24 대화로그 기록 의무**: 주요 작업·결정·설계 시 `공유/대화로그/{프로젝트}/YYYY-MM-DD.md`에 엔트리 추가. **결정·설계 엔트리는 기각안 필드 필수** (세계관·네이밍·스토리 아크 결정 시 검토했으나 채택하지 않은 안 + 기각 사유 명시, 공란 금지). 단순 진행 엔트리는 선택.
|
||||
- **C29-4 완료 후 동기화**: 업무 완료 시 PD 지시 로그 상태 갱신(`완료` + 산출물 경로) + 대화로그 엔트리 + 소통 채널 `완료/` 이동 + Live 더미(`.live/`) 기록을 세트로 수행.
|
||||
- **plan-auditor 모드 A 권장**: 세계관 핵심 결정·네이밍 체계 확정 응답 발신 전 `plan-auditor` 감사관 모드 A 호출로 교차 검증(P27-1).
|
||||
|
||||
|
|
|
|||
|
|
@ -33,10 +33,20 @@ model: sonnet
|
|||
|
||||
## 공통 업무 규칙
|
||||
|
||||
> `공유/공통_업무_규칙.md`의 규칙(핵심 규칙 C1~C13 / 프로젝트 규칙 P1~P20)을 준수한다. 핵심 규칙은 위반 불가.
|
||||
> 특히 **C7 재미 우선 원칙**, **C3 이슈 은폐 금지·즉시 보고**, **P17 ★ 조건 배타 배치 규칙**을 엄수한다.
|
||||
> `.claude/skills/너드나비스-코어룰/SKILL.md` 단일 SOT(C1~C31 / P1~P27)를 Skill 자동 주입으로 준수한다. 핵심 규칙은 위반 불가.
|
||||
> 팀원은 팀장에게 확인 후 진행하고, 규칙 변경이 필요하면 팀장에게 건의한다.
|
||||
>
|
||||
> **C13·P19 PD 지시 트래킹 의무 (헌법급)**: PD님 직접 지시를 인지한 즉시 `공유/PD_지시_트래킹/기획팀_PD_지시_로그.md`에 등록. 시작·진행·완료·중단(사유+사후 조치) 4단계 전부 가시화. 팀장이 등록 못한 경우 팀원이 자체 등록 가능. 누락 시 C3·C13 위반.
|
||||
> **P20 일일 보고 의무**: 주요 작업 단계 종료 시 `공유/일일보고/YYYY-MM-DD_기획팀.md` 갱신(append). 포함: PD 지시 반영 / 자율 작업 / 발견 이슈 / 다음 예정.
|
||||
|
||||
## 기록 의무 (2026-04-17 개정 — 영역 특화)
|
||||
|
||||
**영역 특화 준수 사항**
|
||||
- **P18 설계 문서화 의무 (시스템 설계 1차 대상)**: 핵심 게임 루프·메카닉·시스템 규칙은 반드시 별도 설계 문서로 명문화. 유령 문서(참조만 남고 본문 부재) 금지. 설계 변경·대체(코어 교체 등) 시 신규 설계안 문서 필수, 기존 문서는 "대체됨" 표시 후 보관. 필수 포함: 결정 배경·선택 방향과 대안(trade-off)·구현 가이드라인·검증 방법·변경 이력.
|
||||
- **C3 이슈 은폐 금지**: 시스템 간 충돌·논리 모순 발견 즉시 팀장에게 보고.
|
||||
- **C7 재미 우선 원칙**: 이 시스템이 풀고자 하는 플레이어 문제·제공하는 경험을 먼저 정의. "재미있게 전투한다" 같은 추상 표현 금지.
|
||||
- **P17 ★ 조건 배타 배치 규칙**: 시스템 신규 규칙 도입 시 기존 컨텐츠·레벨 설계와 배타 조합 정합성 확인.
|
||||
|
||||
**공통 기록 의무 (전 에이전트 공통)**
|
||||
- **C13·P19 PD 지시 트래킹 (헌법급)**: PD님 직접 지시 인지 즉시 `공유/PD_지시_트래킹/기획팀_PD_지시_로그.md` 등록. 4단계(시작·진행·완료·중단) 전부 가시화. 누락 시 C3·C13 위반.
|
||||
- **P24 대화로그 기록 의무**: 주요 작업·결정·설계 시 `공유/대화로그/{프로젝트}/YYYY-MM-DD.md`에 엔트리 추가. **결정·설계 엔트리는 기각안 필드 필수** (시스템 구조·메카닉 결정 시 검토했으나 채택하지 않은 안 + 기각 사유 명시, 공란 금지). 단순 진행 엔트리는 선택.
|
||||
- **C29-4 완료 후 동기화**: 업무 완료 시 PD 지시 로그 상태 갱신(`완료` + 산출물 경로) + 대화로그 엔트리 + 소통 채널 `완료/` 이동 + Live 더미(`.live/`) 기록을 세트로 수행.
|
||||
- **plan-auditor 모드 A 권장**: 시스템 설계 확정·메카닉 변경 응답 발신 전 `plan-auditor` 감사관 모드 A 호출로 교차 검증(P27-1).
|
||||
|
||||
|
|
|
|||
|
|
@ -43,10 +43,19 @@ model: sonnet
|
|||
|
||||
## 공통 업무 규칙
|
||||
|
||||
> `공유/공통_업무_규칙.md`의 규칙(핵심 규칙 C1~C13 / 프로젝트 규칙 P1~P20)을 준수한다. 핵심 규칙은 위반 불가.
|
||||
> 특히 **C7 재미 우선 원칙**을 엄수한다.
|
||||
> `.claude/skills/너드나비스-코어룰/SKILL.md` 단일 SOT(C1~C31 / P1~P27)를 Skill 자동 주입으로 준수한다. 핵심 규칙은 위반 불가.
|
||||
> 팀원은 팀장에게 확인 후 진행하고, 규칙 변경이 필요하면 팀장에게 건의한다.
|
||||
>
|
||||
> **C13·P19 PD 지시 트래킹 의무 (헌법급)**: PD님 직접 지시를 인지한 즉시 `공유/PD_지시_트래킹/기획팀_PD_지시_로그.md`에 등록. 시작·진행·완료·중단(사유+사후 조치) 4단계 전부 가시화. 팀장이 등록 못한 경우 팀원이 자체 등록 가능. 누락 시 C3·C13 위반.
|
||||
> **P20 일일 보고 의무**: 주요 작업 단계 종료 시 `공유/일일보고/YYYY-MM-DD_기획팀.md` 갱신(append). 포함: PD 지시 반영 / 자율 작업 / 발견 이슈 / 다음 예정.
|
||||
|
||||
## 기록 의무 (2026-04-17 개정 — 영역 특화)
|
||||
|
||||
**영역 특화 준수 사항**
|
||||
- **P18 설계 문서화 의무 (UI/UX 플로우 1차 대상)**: 화면 플로우·정보 구조·조작 스킴 설계는 반드시 별도 문서로 명문화. 유령 문서(참조만 남고 본문 부재) 금지. 설계 변경 시 신규 문서 + 기존 "대체됨" 표시. 플로우 다이어그램·화면별 정보 우선순위·예외 경로를 포함.
|
||||
- **C7 재미 우선 원칙**: UI가 플레이어에게 주는 경험(즉각적 피드백·한 화면 한 결정)을 명확히 정의.
|
||||
- **접근성 기본 원칙**: 접근성은 추가가 아니라 기본. 처음부터 고려.
|
||||
|
||||
**공통 기록 의무 (전 에이전트 공통)**
|
||||
- **C13·P19 PD 지시 트래킹 (헌법급)**: PD님 직접 지시 인지 즉시 `공유/PD_지시_트래킹/기획팀_PD_지시_로그.md` 등록. 4단계(시작·진행·완료·중단) 전부 가시화. 누락 시 C3·C13 위반.
|
||||
- **P24 대화로그 기록 의무**: 주요 작업·결정·설계 시 `공유/대화로그/{프로젝트}/YYYY-MM-DD.md`에 엔트리 추가. **결정·설계 엔트리는 기각안 필드 필수** (화면 구조·플로우 결정 시 검토했으나 채택하지 않은 안 + 기각 사유 명시, 공란 금지). 단순 진행 엔트리는 선택.
|
||||
- **C29-4 완료 후 동기화**: 업무 완료 시 PD 지시 로그 상태 갱신(`완료` + 산출물 경로) + 대화로그 엔트리 + 소통 채널 `완료/` 이동 + Live 더미(`.live/`) 기록을 세트로 수행.
|
||||
- **plan-auditor 모드 A 권장**: UI/UX 구조 확정·신규 화면 도입 응답 발신 전 `plan-auditor` 감사관 모드 A 호출로 교차 검증(P27-1).
|
||||
|
||||
|
|
|
|||
|
|
@ -943,6 +943,13 @@ grep -r "기각안" 공유/대화로그/ # 기각 이유 추적
|
|||
- 세션 말미에 **모드 B 주기 감사** 최소 1회 수행
|
||||
- 3개 감사관은 상호 교차 검증 가능 (dev-auditor 결과를 pm-auditor가 메타 검증 등)
|
||||
|
||||
**감사관 호출 주체 (2026-04-17 추가 — 구조적 제약 명시화)**:
|
||||
- Claude Code 서브에이전트는 **자기 세션 내부에서 Task 도구로 타 Agent를 재호출할 수 없다** (환경 제약)
|
||||
- 따라서 **감사관(pm/dev/plan-auditor) 호출 주체는 항상 상위 세션의 PM**이다
|
||||
- 개발팀장·기획팀장이 "dev/plan-auditor 모드 A·B 호출이 필요하다"고 판단한 경우, **호출 요청을 PM에게 이관**하여 PM이 대리 수행 (2026-04-17 개발팀장·기획팀장 양측 실증으로 구조 확인)
|
||||
- PM은 팀장급의 감사관 호출 요청을 **선택 아닌 의무**로 처리 (C29 자율 수행 원칙상 팀장이 판단한 필요는 존중)
|
||||
- 본 제약은 Claude Code의 환경 제약에 종속되므로 향후 환경 변경 시 본 조항 재검토
|
||||
|
||||
### P27-2. Agent 호출 이력 기록 의무 (신설 / 2026-04-17 호출 프롬프트 3요소 추가)
|
||||
|
||||
PM 또는 어떤 에이전트가 Agent 도구로 다른 에이전트를 호출할 때:
|
||||
|
|
|
|||
|
|
@ -0,0 +1,267 @@
|
|||
#!/usr/bin/env node
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const {
|
||||
Document, Packer, Paragraph, TextRun, Table, TableRow, TableCell,
|
||||
HeadingLevel, AlignmentType, BorderStyle, WidthType, ShadingType,
|
||||
LevelFormat, TableOfContents, PageBreak, TabStopType, TabStopPosition
|
||||
} = require('docx');
|
||||
|
||||
const srcPath = process.argv[2];
|
||||
const dstPath = process.argv[3];
|
||||
if (!srcPath || !dstPath) { console.error('usage: md_to_docx.js <src.md> <dst.docx>'); process.exit(1); }
|
||||
|
||||
const FONT = 'Malgun Gothic';
|
||||
const raw = fs.readFileSync(srcPath, 'utf8');
|
||||
|
||||
// Strip YAML frontmatter
|
||||
let md = raw;
|
||||
if (md.startsWith('---')) {
|
||||
const end = md.indexOf('\n---', 3);
|
||||
if (end > 0) md = md.slice(end + 4).replace(/^\s*\n/, '');
|
||||
}
|
||||
|
||||
const lines = md.split(/\r?\n/);
|
||||
|
||||
const border = { style: BorderStyle.SINGLE, size: 6, color: 'CCCCCC' };
|
||||
const cellBorders = { top: border, bottom: border, left: border, right: border };
|
||||
|
||||
function run(text, opts = {}) {
|
||||
return new TextRun({ text, font: FONT, size: opts.size || 22, bold: !!opts.bold, italics: !!opts.italic });
|
||||
}
|
||||
|
||||
// Parse inline **bold**, *italic*, `code` -> TextRun[]
|
||||
function parseInline(text, baseOpts = {}) {
|
||||
const runs = [];
|
||||
const re = /(\*\*[^*]+\*\*|\*[^*]+\*|`[^`]+`)/g;
|
||||
let last = 0; let m;
|
||||
while ((m = re.exec(text)) !== null) {
|
||||
if (m.index > last) runs.push(run(text.slice(last, m.index), baseOpts));
|
||||
const tok = m[0];
|
||||
if (tok.startsWith('**')) runs.push(run(tok.slice(2, -2), { ...baseOpts, bold: true }));
|
||||
else if (tok.startsWith('`')) runs.push(new TextRun({ text: tok.slice(1, -1), font: 'Consolas', size: baseOpts.size || 22 }));
|
||||
else runs.push(run(tok.slice(1, -1), { ...baseOpts, italic: true }));
|
||||
last = m.index + tok.length;
|
||||
}
|
||||
if (last < text.length) runs.push(run(text.slice(last), baseOpts));
|
||||
if (runs.length === 0) runs.push(run(text, baseOpts));
|
||||
return runs;
|
||||
}
|
||||
|
||||
function para(text, opts = {}) {
|
||||
return new Paragraph({
|
||||
children: parseInline(text, opts),
|
||||
spacing: { before: 60, after: 60 },
|
||||
...(opts.heading ? { heading: opts.heading } : {}),
|
||||
});
|
||||
}
|
||||
|
||||
function heading(text, level) {
|
||||
const map = { 1: HeadingLevel.HEADING_1, 2: HeadingLevel.HEADING_2, 3: HeadingLevel.HEADING_3, 4: HeadingLevel.HEADING_4 };
|
||||
const size = { 1: 36, 2: 30, 3: 26, 4: 24 }[level] || 22;
|
||||
return new Paragraph({
|
||||
heading: map[level] || HeadingLevel.HEADING_4,
|
||||
children: [new TextRun({ text, font: FONT, size, bold: true })],
|
||||
spacing: { before: 240, after: 120 },
|
||||
});
|
||||
}
|
||||
|
||||
function bullet(text, level = 0) {
|
||||
return new Paragraph({
|
||||
numbering: { reference: 'bullets', level },
|
||||
children: parseInline(text),
|
||||
spacing: { before: 40, after: 40 },
|
||||
});
|
||||
}
|
||||
|
||||
function numbered(text, level = 0) {
|
||||
return new Paragraph({
|
||||
numbering: { reference: 'numbers', level },
|
||||
children: parseInline(text),
|
||||
spacing: { before: 40, after: 40 },
|
||||
});
|
||||
}
|
||||
|
||||
function codeBlock(text) {
|
||||
return new Paragraph({
|
||||
children: [new TextRun({ text, font: 'Consolas', size: 20 })],
|
||||
shading: { type: ShadingType.CLEAR, fill: 'F4F4F4' },
|
||||
spacing: { before: 60, after: 60 },
|
||||
});
|
||||
}
|
||||
|
||||
function quote(text) {
|
||||
return new Paragraph({
|
||||
children: parseInline(text, { italic: true }),
|
||||
indent: { left: 360 },
|
||||
spacing: { before: 60, after: 60 },
|
||||
border: { left: { style: BorderStyle.SINGLE, size: 18, color: '2E75B6', space: 12 } },
|
||||
});
|
||||
}
|
||||
|
||||
// Parse pipe table starting at index i, returns { table, nextIndex }
|
||||
function parseTable(startIdx) {
|
||||
const rows = [];
|
||||
let i = startIdx;
|
||||
while (i < lines.length && /^\s*\|.*\|\s*$/.test(lines[i])) {
|
||||
rows.push(lines[i].trim());
|
||||
i++;
|
||||
}
|
||||
if (rows.length < 2) return null;
|
||||
// Header | separator | body
|
||||
const split = (r) => r.slice(1, -1).split('|').map(c => c.trim());
|
||||
const header = split(rows[0]);
|
||||
const body = rows.slice(2).map(split);
|
||||
const colCount = header.length;
|
||||
const totalWidth = 9000;
|
||||
const colWidth = Math.floor(totalWidth / colCount);
|
||||
const columnWidths = new Array(colCount).fill(colWidth);
|
||||
|
||||
const makeCell = (txt, isHeader) => new TableCell({
|
||||
borders: cellBorders,
|
||||
width: { size: colWidth, type: WidthType.DXA },
|
||||
shading: isHeader ? { type: ShadingType.CLEAR, fill: 'D5E8F0' } : undefined,
|
||||
margins: { top: 80, bottom: 80, left: 120, right: 120 },
|
||||
children: [new Paragraph({ children: parseInline(txt, { bold: isHeader, size: 20 }) })],
|
||||
});
|
||||
|
||||
const tableRows = [
|
||||
new TableRow({ children: header.map(h => makeCell(h, true)) }),
|
||||
...body.map(r => new TableRow({ children: r.concat(new Array(Math.max(0, colCount - r.length)).fill('')).slice(0, colCount).map(c => makeCell(c, false)) }))
|
||||
];
|
||||
return {
|
||||
table: new Table({ width: { size: totalWidth, type: WidthType.DXA }, columnWidths, rows: tableRows }),
|
||||
nextIndex: i,
|
||||
};
|
||||
}
|
||||
|
||||
const children = [];
|
||||
// Cover
|
||||
children.push(new Paragraph({
|
||||
children: [new TextRun({ text: '인간 서버 개발자 업무 지시서', font: FONT, size: 44, bold: true })],
|
||||
alignment: AlignmentType.CENTER,
|
||||
spacing: { before: 400, after: 200 },
|
||||
}));
|
||||
children.push(new Paragraph({
|
||||
children: [new TextRun({ text: '수상한잡화점 서버 파트 — v1.0', font: FONT, size: 28 })],
|
||||
alignment: AlignmentType.CENTER,
|
||||
spacing: { after: 120 },
|
||||
}));
|
||||
children.push(new Paragraph({
|
||||
children: [new TextRun({ text: '발행: 개발팀장 · 수신: 인간 서버 개발자 · 일자: 2026-04-17', font: FONT, size: 22, italics: true })],
|
||||
alignment: AlignmentType.CENTER,
|
||||
spacing: { after: 600 },
|
||||
}));
|
||||
children.push(new Paragraph({ children: [new PageBreak()] }));
|
||||
|
||||
// Manual index (no TOC field, avoids Word's external-reference warning)
|
||||
children.push(new Paragraph({
|
||||
children: [new TextRun({ text: '목차', font: FONT, size: 32, bold: true })],
|
||||
spacing: { before: 120, after: 240 },
|
||||
}));
|
||||
for (const L of lines) {
|
||||
const hm = /^(#{1,3})\s+(.*)$/.exec(L);
|
||||
if (!hm) continue;
|
||||
const lv = hm[1].length;
|
||||
const indent = (lv - 1) * 360;
|
||||
children.push(new Paragraph({
|
||||
children: [new TextRun({ text: hm[2], font: FONT, size: 22 })],
|
||||
indent: { left: indent },
|
||||
spacing: { before: 20, after: 20 },
|
||||
}));
|
||||
}
|
||||
children.push(new Paragraph({ children: [new PageBreak()] }));
|
||||
|
||||
let i = 0;
|
||||
while (i < lines.length) {
|
||||
const line = lines[i];
|
||||
// Table
|
||||
if (/^\s*\|.*\|\s*$/.test(line) && i + 1 < lines.length && /^\s*\|[\s:\-|]+\|\s*$/.test(lines[i + 1])) {
|
||||
const result = parseTable(i);
|
||||
if (result) { children.push(result.table); children.push(new Paragraph({ children: [new TextRun('')] })); i = result.nextIndex; continue; }
|
||||
}
|
||||
// Heading
|
||||
const hMatch = /^(#{1,6})\s+(.*)$/.exec(line);
|
||||
if (hMatch) { children.push(heading(hMatch[2], Math.min(hMatch[1].length, 4))); i++; continue; }
|
||||
// Code block
|
||||
if (/^```/.test(line)) {
|
||||
const buf = [];
|
||||
i++;
|
||||
while (i < lines.length && !/^```/.test(lines[i])) { buf.push(lines[i]); i++; }
|
||||
i++;
|
||||
if (buf.length) children.push(codeBlock(buf.join('\n')));
|
||||
continue;
|
||||
}
|
||||
// Quote
|
||||
if (/^>\s?/.test(line)) {
|
||||
children.push(quote(line.replace(/^>\s?/, '')));
|
||||
i++; continue;
|
||||
}
|
||||
// Horizontal rule
|
||||
if (/^---+\s*$/.test(line)) {
|
||||
children.push(new Paragraph({ border: { bottom: { style: BorderStyle.SINGLE, size: 6, color: '999999', space: 1 } }, spacing: { before: 120, after: 120 } }));
|
||||
i++; continue;
|
||||
}
|
||||
// Bullet
|
||||
const bulletMatch = /^(\s*)[-*]\s+(.*)$/.exec(line);
|
||||
if (bulletMatch) {
|
||||
const level = Math.min(Math.floor(bulletMatch[1].length / 2), 3);
|
||||
children.push(bullet(bulletMatch[2], level));
|
||||
i++; continue;
|
||||
}
|
||||
// Numbered
|
||||
const numMatch = /^(\s*)\d+\.\s+(.*)$/.exec(line);
|
||||
if (numMatch) {
|
||||
const level = Math.min(Math.floor(numMatch[1].length / 2), 3);
|
||||
children.push(numbered(numMatch[2], level));
|
||||
i++; continue;
|
||||
}
|
||||
// Empty
|
||||
if (/^\s*$/.test(line)) { i++; continue; }
|
||||
// Paragraph
|
||||
children.push(para(line));
|
||||
i++;
|
||||
}
|
||||
|
||||
const doc = new Document({
|
||||
creator: '너드나비스 개발팀',
|
||||
title: '인간 서버 개발자 업무 지시서 — 수상한잡화점',
|
||||
styles: {
|
||||
default: { document: { run: { font: FONT, size: 22 } } },
|
||||
paragraphStyles: [
|
||||
{ id: 'Heading1', name: 'Heading 1', basedOn: 'Normal', next: 'Normal', quickFormat: true,
|
||||
run: { size: 36, bold: true, font: FONT, color: '1F3864' },
|
||||
paragraph: { spacing: { before: 360, after: 180 }, outlineLevel: 0 } },
|
||||
{ id: 'Heading2', name: 'Heading 2', basedOn: 'Normal', next: 'Normal', quickFormat: true,
|
||||
run: { size: 30, bold: true, font: FONT, color: '2E75B6' },
|
||||
paragraph: { spacing: { before: 280, after: 140 }, outlineLevel: 1 } },
|
||||
{ id: 'Heading3', name: 'Heading 3', basedOn: 'Normal', next: 'Normal', quickFormat: true,
|
||||
run: { size: 26, bold: true, font: FONT },
|
||||
paragraph: { spacing: { before: 200, after: 100 }, outlineLevel: 2 } },
|
||||
{ id: 'Heading4', name: 'Heading 4', basedOn: 'Normal', next: 'Normal', quickFormat: true,
|
||||
run: { size: 24, bold: true, font: FONT },
|
||||
paragraph: { spacing: { before: 160, after: 80 }, outlineLevel: 3 } },
|
||||
],
|
||||
},
|
||||
numbering: {
|
||||
config: [
|
||||
{ reference: 'bullets', levels: [0, 1, 2, 3].map(lv => ({ level: lv, format: LevelFormat.BULLET, text: ['•','◦','▪','▫'][lv], alignment: AlignmentType.LEFT, style: { paragraph: { indent: { left: 720 * (lv + 1), hanging: 360 } } } })) },
|
||||
{ reference: 'numbers', levels: [0, 1, 2, 3].map(lv => ({ level: lv, format: LevelFormat.DECIMAL, text: `%${lv+1}.`, alignment: AlignmentType.LEFT, style: { paragraph: { indent: { left: 720 * (lv + 1), hanging: 360 } } } })) },
|
||||
],
|
||||
},
|
||||
sections: [{
|
||||
properties: {
|
||||
page: {
|
||||
size: { width: 11906, height: 16838 }, // A4
|
||||
margin: { top: 1440, right: 1440, bottom: 1440, left: 1440 },
|
||||
},
|
||||
},
|
||||
children,
|
||||
}],
|
||||
});
|
||||
|
||||
Packer.toBuffer(doc).then(buf => {
|
||||
fs.mkdirSync(path.dirname(dstPath), { recursive: true });
|
||||
fs.writeFileSync(dstPath, buf);
|
||||
console.log('OK', dstPath, buf.length, 'bytes');
|
||||
});
|
||||
|
|
@ -32,9 +32,9 @@ C3·C13 위반에 해당. **즉시 자진 보고 후 소급 등록**.
|
|||
| # | 일시 | 지시 요지 | 처리 상태 | 산출물 경로 | 중단 사유 | 사후 조치 |
|
||||
|---|------|----------|----------|-----------|----------|----------|
|
||||
| 28 | 2026-04-16 | (PD님 직접 지시, 총괄PM 경유) 기획팀 밸런스 작업을 위한 시뮬레이션 대응 — 07 착수계획(시뮬레이터 이원화 해소) 진행 상태 보고 + 기획팀이 밸런스 작업에 사용할 수 있는 시뮬레이션 환경 구축. **2026-04-17 PD님 직접 지시로 Unity MCP 기반 시뮬레이션 방향으로 전환** (커밋 `db64310`). | 진행중 | `공유/소통/PM→개발팀/2026-04-16_REQ_시뮬레이션_대응_기획팀_밸런스작업_지원.md`, `공유/소통/개발팀→PM/2026-04-16_RPT_시뮬레이션_대응_현황보고.md`, **`공유/소통/개발팀→PM/2026-04-17_Unity_MCP_시뮬레이션_기술검토_개발팀.md` (2026-04-17 Unity MCP 방향 기술검토 완료)** | - | **2026-04-17 PD님 해석 확정**: 헌법 제1원칙 목표 2 = "코어 코드 프레임워크 차기 프로젝트 활용" 의미. 차기 프로젝트 Unity 전제 여부는 본 목표와 무관 → 개발팀장 상신 사항 해소. **Python 시뮬 파일 소실 확정 — 폐기 사안, 교차 검증 축 단일(Unity MCP)로 확정**. Phase 3 재개 로드맵은 PD님 별도 논의 예정 | 07_v2·11·12 신설 착수는 PD님 Phase 3 재개 논의 후 재개 (PM 재량 대기) |
|
||||
| 1 | 2026-04-14 | NerdNavisCore 타 회사 소유 전환·담당자 퇴사 사실 통보, 자체 범용 코어 신규 제작 결정 | 진행중 | `프로젝트/수상한잡화점/개발/06_신규코어_설계안_v1.md` (초안, 2026-04-16 재구조 시 경로 이관) / **구 `개발팀/코어_설계/` 흡수 완료 판정 (2026-04-17 개발팀장 실측, pm-auditor Critical C1 해소)**: ①`01_아키텍처_개요_v1.md` → `프로젝트/코어프레임워크/01_아키텍처_개요_v1.md` (커밋 `1f50ce5` 이관), ②`02_수상한잡화점_추출대상_v1.md` → `프로젝트/코어프레임워크/02_수상한잡화점_추출대상_v1.md` (동 커밋 이관), ③`_skeleton/` → `코어코드/NerdNavis.Framework/Runtime·Editor/` (커밋 `7187ac6` 코어코드 레포 통합으로 실체 구현 발전적 흡수) / **`코어코드/NerdNavis.Framework/Runtime/Core/` 구현체 — Tier 1 기반 Core 4종 완료 (Log·CoroutineRunner·MonoSingleton·ServiceLocator + 테스트 28건) + `코어코드/NerdNavis.Framework/` git 통합 완료 (커밋 `7187ac6`)** | - | OI-1(네임스페이스 NerdNavis.*) PD님 확정 반영 완료. **OI-2 C+H1 PD님 승인 완료** (2026-04-16 교차 검증으로 확인, 커밋 `7187ac6` 메시지에 명시). OI-3 불요 결정, OI-4 A안 확정, OI-5 폐기. Tier 1+2 MVP 범위 PD님 확정 반영. **Tier 1 잔여 9종 모듈 구현 미착수** (차단 요인 없음, 즉시 착수 가능) |
|
||||
| 1 | 2026-04-14 | NerdNavisCore 타 회사 소유 전환·담당자 퇴사 사실 통보, 자체 범용 코어 신규 제작 결정 | 진행중 | `프로젝트/수상한잡화점/개발/06_신규코어_설계안_v1.md` / `프로젝트/코어프레임워크/01_아키텍처_개요_v1.md` / `프로젝트/코어프레임워크/02_수상한잡화점_추출대상_v1.md` / `프로젝트/코어프레임워크/03_배포방식_안건_v1.md` / `코어코드/NerdNavis.Framework/` (Tier 1 Core 4종 + **Attribute 3종 + Util 6종 = 신규 9종 구현 완료 2026-04-17**: ReadOnly/ShowIf/ArrayTitle 3종 + EnumToInt/EnumEx/FormatEx/MathEx/KeyMaker/ValidationEx 6종 + 각 단위 테스트) | - | **Tier 1 잔여 구현 부분 완료 (2026-04-17 마무리 지시)**. 16종 중 13종 완료, 잔여 Data/Event/Container 등은 상호작용 설계 재검증 필요. 경로 정규화 완료로 `verify_log_paths.sh` 감사 15건 전수 실존 확인 통과. OI-1·OI-2 C+H1·OI-3·OI-4 확정, OI-5 폐기 |
|
||||
| 2 | 2026-04-14 | 서버 Critical 보안 3건 보류 | 보류 | `프로젝트/수상한잡화점/개발/05_서버연동_현황_v1.md` (2026-04-16 재구조 시 경로 이관, 2026-04-17 pm-auditor 감사 Critical C1 정정) | 서버 파트 정비 미완료 (PD님 지시) | 서버팀 가동 시점에 블로커급 재개. 담당: 서버팀장. 재개 트리거: 서버 파트 정비 완료 통보 |
|
||||
| 5 | 2026-04-15 | (3대 지시) **A.** Framework Tier 1 기반 Core 모듈 구현 착수 (Logger·ServiceLocator·CoroutineRunner 등 — 설계 문서 재확인 후 파일 단위). **B.** 수상한 잡화점 Phase 0-B/C 재개. **C.** 위 내용을 총괄PM에게도 보고 | 진행중 | **A 완료분**: `코어코드/NerdNavis.Framework/Runtime/Core/**` (Log·CoroutineRunner·MonoSingleton·ServiceLocator 4종 + 테스트 28건). / **B-1/B-2/B-3 완료**: `프로젝트/수상한잡화점/개발/08_전투시스템_SOT_v1.md`, `09_카드시스템_아키텍처_v1.md`, `10_데이터로딩_구조_v1.md`. / **C 일괄 공유 완료** | - | Tier 1 잔여 9종 미구현 + Phase 0-C(Q-P1/P2/P3 응답서·시뮬레이터 전략) 미착수. 시뮬레이션 트랙은 2026-04-17 PD님 지시로 Unity MCP 활용 방향으로 전환(#28에서 통합 관리). |
|
||||
| 5 | 2026-04-15 | (3대 지시) **A.** Framework Tier 1 기반 Core 모듈 구현 착수 (Logger·ServiceLocator·CoroutineRunner 등 — 설계 문서 재확인 후 파일 단위). **B.** 수상한 잡화점 Phase 0-B/C 재개. **C.** 위 내용을 총괄PM에게도 보고 | 진행중 | **A 부분 완료** (#1 항목 참조 — 2026-04-17 Attribute 3종 + Util 6종 추가 구현). **B-1/B-2/B-3 완료**: `프로젝트/수상한잡화점/개발/08_전투시스템_SOT_v1.md` · `프로젝트/수상한잡화점/개발/09_카드시스템_아키텍처_v1.md` · `프로젝트/수상한잡화점/개발/10_데이터로딩_구조_v1.md`. **B-4/B-5 완료 2026-04-17**: `프로젝트/수상한잡화점/개발/11_UI아키텍처_v1.md` · `프로젝트/수상한잡화점/개발/12_메타시스템_v1.md`. **C-Phase0-C 부분 완료 2026-04-17**: `공유/소통/개발팀→PM/2026-04-17_Phase0-C_QP_응답서_개발팀.md` (Q-P1 기획 의도 영역 환송 + Q-P2 초벌 스캔 + 시뮬레이터 전략 v2 Unity MCP 단일축 재설정). **C 일괄 공유 완료** | - | Q-P2 정밀 2차 응답 + Unity MCP 시뮬레이션 인프라 4종 (SimulationRunner 프로토타입·파라미터 외부화·결과 JSON 스키마·MCP 호출 스니펫) 구현은 후속 작업 |
|
||||
|
||||
> **2026-04-15 오후 추가 갱신 (C4·C13 위반 자진 정정 2차)**:
|
||||
> #5번 신규 등재. PD님 3대 지시(A/B/C) 및 #1 산출물 경로에 Framework Tier 1 구현체(`D:/NerdNavis/NerdNavis.Framework/`)를 소급 등록. **B 착수 시점 및 Git 동기화 병렬 지시(#4) 착수 시점에 총괄PM 공유를 누락**한 건을 PD님이 직접 지적하여 즉시 정정. 근본 원인: "C 항목 진행 전 지시 대기" 지시를 본인이 **PM 공유 전체 보류**로 잘못 확대 해석. C4(총괄PM 하달)·C13(4단계 가시화)의 "작업 착수 시점=상시 공유 의무" 원칙을 거스른 것. 재발 방지 관례: **신규 트랙 착수 즉시 pm-general 공유 → TodoWrite 항목 생성** (총괄PM 채택 권고). 자체 경위는 `공유/일일보고/2026-04-15_개발팀.md` 오후 섹션 참조.
|
||||
|
|
@ -87,6 +87,10 @@ C3·C13 위반에 해당. **즉시 자진 보고 후 소급 등록**.
|
|||
|
||||
| # | 일시 | 지시 요지 | 처리 상태 | 산출물 경로 | 중단 사유 | 사후 조치 |
|
||||
|---|------|----------|----------|-----------|----------|----------|
|
||||
| 32 | 2026-04-17 | (PD님 직접 지시, PM 경유) **서버 작업 참고 자료 v1.2 재작성 (외부 서버 작업자용 중립화)** — (1) PlayFab 전제 제거(현 사용 중 상태로만 중립 기술, 스택 선택은 열린 결정 사항), (2) 조직 내부 프로세스 내용 전면 제거(코어룰 참조·PD 지시 번호·결정 대기 섹션·frontmatter related/depends_on·기각안 섹션 삭제), (3) 문서 성격 재정의("인간 서버 개발자 업무 지시서" → "서버 작업 참고 자료", 지시형 → 제공형 톤). 신규 파일로 분리 작성, v1.1은 조직 내부용 상세본으로 보존 | **완료** | `공유/소통/개발팀→PM/2026-04-17_서버_작업_참고자료.md` (v1.2, 외부 서버 작업자용). frontmatter: type: 참고자료, audience: 외부 서버 작업자. "인간 서버 개발자"·"인간 작업자" 단어 전부 제거. v1.1(조직 내부용 상세본)은 원 경로 유지 | - | DOCX 변환은 PM이 `anthropic-skills:docx`로 재생성. 외부 전달 시 v1.2 사용, 조직 내부 참조는 v1.1 상세본 유지 |
|
||||
| 31 | 2026-04-17 | (PD님 직접 지시, PM 경유) **서버 개발자 지시서 v1.1 요약판 재작성** — (1) 어뷰징 판정 책임 클라 100% 재확정 (서버는 `is_abuse_flag` 수신만, 경계값 보관·검증 안 함), (2) 인간 개발자 5~7분 완독 가능한 요약판으로 v1.0(446줄) 전면 재작성. 개발팀장 수행 | **완료** | `공유/소통/개발팀→PM/2026-04-17_서버개발자_업무지시서_최종본.md` (v1.1, 207줄). 섹션 5 "어뷰징 방지 — 클라 주도, 서버 최소 역할"로 정정. 샘플 API 1건(`Save_StageResult`) 유지, 템플릿·매트릭스 세부 제거. 기각안 5종 명시(v1.0 B-7 구조 폐기 포함) | - | DOCX는 PM이 `anthropic-skills:docx`로 재생성. 후속 PD-③·PD-④는 인간 개발자 배정 후 수렴 |
|
||||
| 30 | 2026-04-17 | (PD님 직접 지시, PM 경유) **인간 서버 개발자 업무 지시서 최종본 작성** — PD 확정 3건 반영(보상 재화 통일·어뷰징 방지 기획팀 주도·DOCX 변환 제작). 개발팀장 최종본 md 작성, DOCX 변환은 PM 수행 | **완료 (v1.1로 대체)** | `공유/소통/개발팀→PM/2026-04-17_서버개발자_업무지시서_최종본.md` (v1.0 → v1.1로 재작성, 2026-04-17 PD님 재결정). B-7 서버 경계값 검증 구조는 #31에서 폐기 | - | v1.0 상태는 #31(v1.1 재작성) 기각안 섹션에 영구 보존. 본 항목은 초판 작성 완료로 마감 |
|
||||
| 29 | 2026-04-17 | (PD님 직접 지시, PM 경유) 인간 서버 개발자 업무 지시서 **초안** 작성 — 개발팀이 PM과 상의해서 서버 역할·클라/서버 경계·PD 결정 안건을 정리해 보고 | **완료** (최종본 #30으로 승격) | `공유/소통/완료/2026-04-17_RPT_서버역할_정리_초안.md` (초안 아카이브 이관). 현 수상한잡화점 `ServerClass.cs`·`ServerInfo.cs` 실측 + PD 결정 안건 5건 초안 제시 | - | PD 확정 3건(보상 재화 통일·어뷰징 방지 기획팀 주도·DOCX 제작) 수령 후 #30 최종본으로 승격 갱신. 본 항목은 초안 작성 완료로 마감 |
|
||||
| 17 | 2026-04-15 | (PD님 직접 승인) **C17-3 보강 + 진입 절차 3요소 의무 + 재발 방지 메모리 신설** | **완료 (실효)** | C17-3 보강분 작성 완료 | 2026-04-16 상위 규칙 C17 폐기(단일 세션 전환)로 실효 | - |
|
||||
| 12 | 2026-04-15 | (PD님 직접 지시) **C17 신설 — 세션 이동 지시 시 복사 가능 명령어 동봉 의무** | **완료 (실효)** | C17 신설 당시 완료 | 2026-04-16 단일 세션 전환으로 C17 자체 폐기되어 목적 소멸 | - |
|
||||
| 3 | 2026-04-14 | (총괄PM 경유) 시뮬레이터 이원화 해소 작업 착수 + 06번 설계안 문서 작성 | **완료** | `프로젝트/수상한잡화점/개발/06_신규코어_설계안_v1.md`, `07_시뮬레이터_이원화_해소_착수계획_v1.md` | - | 착수·문서 작성 완료. 후속 진행은 #28(시뮬레이션 환경 구축)에서 통합 관리. Unity MCP 활용 방향으로 전환(2026-04-17) |
|
||||
|
|
|
|||
|
|
@ -46,6 +46,10 @@ C3·C13 위반에 해당. **즉시 자진 보고 후 소급 등록**.
|
|||
|
||||
| # | 일시 | 지시 요지 | 처리 상태 | 산출물 경로 | 중단 사유 | 사후 조치 |
|
||||
|---|------|----------|----------|-----------|----------|----------|
|
||||
| 35 | 2026-04-17 | 밸런싱 md 4종 변경 이력 테이블 표준화 (팀장 재량 진행 일괄 승인) | **완료** | `프로젝트/수상한잡화점/기획/스테이지난이도곡선_v1.md`·`밸런싱전략_v1.md`·`전체테이블감사_v1.md`·`빌드_조건_충돌점검_v1.md` 각 하단 "변경 이력 (P16 산출물 추적성)" 섹션 신설 + 초기 행 기입 | - | 차기 밸런스 변경 시 표준 포맷으로 1행 append. 필드: 일시/변경자/변경 필드/이전값→이후값/재미 근거/관련 PD 지시# |
|
||||
| 34 | 2026-04-17 | 전문가 에이전트 6종 기록 의무 명시 + 구 P20 잔존 제거 (팀장 재량 진행 일괄 승인) | **완료** | `.claude/agents/balance-designer.md`·`content-designer.md`·`level-designer.md`·`narrative-designer.md`·`system-designer.md`·`ux-designer.md` 각 파일 "공통 업무 규칙" 섹션 교체 + "기록 의무 (영역 특화)" 섹션 신설. 구 P20(일일보고) 문구 전량 제거, SKILL.md 단일 SOT 참조로 통일 | - | PM이 `.live/` 더미 반영 예정. 차기 감사 시 plan-auditor로 준수 여부 교차 검증 |
|
||||
| 33 | 2026-04-17 | 밸런스 요구서 표준 템플릿 신설 (팀장 재량 진행 일괄 승인) | **완료** | `공유/소통/기획팀→개발팀/REQ-템플릿_밸런스수치.md` (9개 섹션: 요구서 식별·변경 필드 목록·변경 전후 수치·재미 근거(C7)·개발 관점 우려 예상(C11)·검증 방법·백업 이력(C6·P16)·기각안(P24)·응답 섹션) | - | 향후 밸런스 수치 요청은 본 템플릿을 복사하여 사용. 템플릿 개선 필요 시 변경 이력 테이블에 반영 |
|
||||
| 32 | 2026-04-17 | 어뷰징 판정 솔루션 기획 — 시뮬레이터 경계값 기반 클라/서버 검증 체계 설계 (기획팀 주도) | **완료** | `공유/소통/기획팀→PM/2026-04-17_어뷰징판정_솔루션_기획서_v1.md` (A~G 7개 섹션 + 기각안 5종). Unity MCP 시뮬 가동 후 경계값 확정은 후속 작업 | - | PM 검토 → 개발팀 F 섹션 인계 → Unity MCP 시뮬 가동(별도 PD 지시) → 경계값 테이블 v1.0.0 산출 → balance-designer 마진 재검토 |
|
||||
| 31 | 2026-04-17 | P24 "기각안" 필드 필수화 — 헌법 제1원칙 목표 2 원칙 B(인사이트 기록) 직결. 기획팀장 `2026-04-17_업무공유체계_점검_기획팀.md` 안건 1 채택 | **완료** | SKILL.md P24 본문 개정(결정·설계 엔트리 필수화 + 기각안 필드 필수화 근거 섹션 신설). 적용 주체: PM·팀장급·전문 에이전트 6종·3축 감사관 공통 | - | 기획팀장·개발팀장.md에 P24 기각안 필수 지침 명시. 향후 기각안 기록률 주기 점검 |
|
||||
| 30 | 2026-04-17 | 기획팀장 맥락 오류(plan-auditor "미신설" 오인) 원인 점검 + 재발 방지 조치 | **완료** | (1) 원인 2중 진단: 기획팀장.md·개발팀장.md가 폐기된 P20(일일보고) 잔존 + P24·P26·P27 미반영 + PM이 Agent 호출 시 최신 헌법급 변경 요지(d33b8be) 프롬프트 누락 (2) 조치: SKILL.md P27-2 "호출 프롬프트 필수 3요소" 추가, 기획팀장·개발팀장.md에 P24·P26·P27·3축 감사관 지침 신설, 구 P20 지침 제거 | - | PM 호출 프롬프트 체크리스트 운영 강제 — 차기 Agent 호출 시 (가)활성 PD 지시 요약 (나)최근 헌법급 변경 요지 (다)관련 신규 에이전트·도구 3요소 필수 포함 |
|
||||
| 27 | 2026-04-16 | 유니티 프로젝트 현재 상태 점검 — 기존 분석 산출물(개발/ 10건, 기획/ 12건) 유효성 교차 검증 | **완료** | `공유/소통/기획팀→PM/2026-04-16_유니티프로젝트_점검_기획팀.md` (8,683 bytes 실측 확인) | - | 후속: xlsm SOT 확정, Spine 도입 현황 개발팀 확인, GameManager.cs 소재 파악 (별도 신규 지시 필요 시 등록) |
|
||||
|
|
|
|||
|
|
@ -1,5 +1,52 @@
|
|||
# 수상한잡화점 — 2026-04-17
|
||||
|
||||
<!-- #PD지시 #기획 #완료 #REQ템플릿 #밸런싱이력 -->
|
||||
## [PM 기록 보완] 기획팀 재량 3건 수상한잡화점 영향 엔트리 (plan-auditor Major M1 시정)
|
||||
- **요지**: 기획팀 #33 밸런스 요구서 표준 템플릿 신설 / #34 전문가 에이전트 6종 기록 의무 명시 + 구 P20 제거 / #35 밸런싱 md 4종(스테이지난이도곡선·밸런싱전략·전체테이블감사·빌드_조건_충돌점검) 변경 이력 테이블 표준화 — 3건 모두 완료
|
||||
- **이유**: 플랜 감사관이 "수상한잡화점 대화로그 누락(조직운영 로그에만 기록)" Major 지적. REQ 템플릿·밸런싱 md 4종은 수상한잡화점 프로젝트 영향이 명확하므로 프로젝트 로그 병기 필요. P27-4 SOT 경계 준수
|
||||
- **산출물**: `공유/소통/기획팀→개발팀/REQ-템플릿_밸런스수치.md` / `.claude/agents/{balance,content,level,narrative,system,ux}-designer.md` / `프로젝트/수상한잡화점/기획/{스테이지난이도곡선_v1,밸런싱전략_v1,전체테이블감사_v1,빌드_조건_충돌점검_v1}.md` 하단 "변경 이력(P16)" 섹션
|
||||
- **기각안**: (1) "조직운영 로그만 기록" — 프로젝트 영향 있는 결정은 프로젝트 로그도 병기 필요 (P27-4), 기각 (2) "REQ 템플릿을 개별 요구서처럼 PD 지시 로그 #화" — 상시 참조 템플릿은 진행 상태 없음, 소통 허브 유지
|
||||
- **상태**: 완료
|
||||
|
||||
<!-- #이슈 #PM #완료 #감사시정 #C30점검기록 -->
|
||||
## [PM 기록 보완] 코어코드 레포 git 점검 기록 (dev-auditor Minor 시정)
|
||||
- **요지**: Tier 1 잔여 9종(Attribute 3 + Util 6) 구현은 외부 레포 `코어코드/NerdNavis.Framework/`에 수행됨. C30 준수 — 작업 착수 시점에 코어코드 레포 git 상태 `nothing to commit, working tree clean` 실측 확인(개발팀장 세션 내부)되었으나 대화로그 1차 엔트리에 기록 누락
|
||||
- **이유**: dev-auditor Minor 지적 — C30-3 점검 결과 증적 미기록. 사후 추적 가능하도록 1줄 보완
|
||||
- **기각안**: 없음 (명백한 누락 보완)
|
||||
- **상태**: 완료
|
||||
|
||||
<!-- #PD지시 #개발 #완료 #서버 #결정 -->
|
||||
## [세션 시점] 서버 개발자 지시서 v1.1 요약판 재작성 완료 (#31, v1.0 대체)
|
||||
- **요지**: PD님 2건 직접 재결정 반영. (1) 어뷰징 판정 책임 = **클라 100%**, 서버는 클라가 보낸 `is_abuse_flag` 수신 시 지급 거부만 수행 (경계값 보관·검증 전부 제거). (2) v1.0 446줄 → v1.1 207줄 요약판으로 전면 재작성 (인간 서버 개발자 5~7분 완독).
|
||||
- **이유**: v1.0의 B-7 "어뷰징 방지 서버 연계" 섹션은 "서버가 경계값 받아 검증" 구조로 설계되어 있었으나 **어뷰징은 서버 개발자 작업 스펙이 아니라 클라 주도 작업**이라는 PD님 재확정으로 전면 정정 필요. 동시에 장문 지시서는 인간 개발자 파악 효율 저하 → 요약판 전환 지시.
|
||||
- **주요 변경**: (a) 섹션 5 "어뷰징 방지 — 클라 주도, 서버 최소 역할"로 정정, Title Data 경계값 적재·IsWithinAbuseThreshold 함수 구조 전부 삭제. (b) 기본 원칙 4종 + 서버 스택 현황 표 + 8개 도메인 표 + 3원칙 + API 3종(샘플 1건만 JSON) + 셋업 체크리스트 + 결정 대기 2건 + 용어집 9개 + 기각안 5건. (c) frontmatter version v1.1, supersedes v1.0 명시.
|
||||
- **산출물**: `공유/소통/개발팀→PM/2026-04-17_서버개발자_업무지시서_최종본.md` (v1.1, 207줄). DOCX는 PM이 `md_to_docx.js`로 재생성 예정.
|
||||
- **상태**: 완료
|
||||
- **기각안**: (1) "어뷰징 경계값 서버 보관·검증" (v1.0 B-7 구조) — PD님 재결정으로 전면 폐기. 서버 개발자 작업 스펙 아님. (2) "상세 설계 전부 포함 장문 지시서" (v1.0 446줄) — 인간 개발자 파악 시간 낭비, 기각. (3) "v1.0 부분 수정" — B-7 전면 재설계 필요 규모, 요약판 원칙 병행 시 전면 재작성이 근본 해결. (4) "샘플 API 전량 제거" — `Save_StageResult` 샘플 1건은 `is_abuse_flag` 인터페이스 실체 예시로 유지 필요. (5) "PD-⑤ 리셋 시간 기준을 결정 대기에 유지" — 개발팀 재량 확정 사항으로 분리 표기.
|
||||
|
||||
<!-- #PD지시 #PM #완료 #DOCX제작 #인간작업자전달 -->
|
||||
## [PM] 인간 작업자 전달용 DOCX 2종 제작 완료
|
||||
- **요지**: PD님 지시("인간 작업자 전달용 문서는 PPT/WORD 등 전달 용이한 파일로 제작") 이행. 서버 개발자 지시서 + 어뷰징 판정 기획서 각 DOCX 변환 완료
|
||||
- **이유**: 인간 서버 개발자 배정 시 즉시 온보딩 가능한 공식 전달 자료 확보. md 원본은 조직 내부 자산으로 유지
|
||||
- **변환 방식**: pandoc 부재로 `scripts/md_to_docx.js`(docx-js 기반) 신설. 한국어 폰트 Malgun Gothic, A4, 표·목차·헤딩 위계·기각안 섹션 보존. 재사용 가능
|
||||
- **산출물**:
|
||||
- `공유/인간작업자_전달자료/2026-04-17_서버개발자_업무지시서_v1.0.docx` (27KB)
|
||||
- `공유/인간작업자_전달자료/2026-04-17_어뷰징판정_솔루션_기획서_v1.0.docx` (21KB)
|
||||
- `scripts/md_to_docx.js` (재사용 변환 도구)
|
||||
- **기각안**:
|
||||
1. PPT 형식 — 서버 개발자는 API·스키마 등 상세 기술 문서가 필요하므로 DOCX가 적합. PPT는 개요·요약 용도
|
||||
2. pandoc 설치 — 환경 의존성 추가 부담. docx-js가 npm 단일 의존으로 더 가벼움
|
||||
3. md 원본 그대로 전달 — PD님이 "전달 용이 파일" 명시, md는 개발자 도구 없으면 가독성 낮음
|
||||
- **상태**: 완료
|
||||
|
||||
<!-- #PD지시 #개발 #완료 #서버 #결정 -->
|
||||
## [세션 시점] 인간 서버 개발자 업무 지시서 최종본 작성 완료 (#30)
|
||||
- **요지**: PD 확정 3건(모든 보상=재화 통일 / 어뷰징 방지 솔루션 기획팀 주도 / DOCX 제작) 반영하여 #29 초안을 최종본으로 승격. B-7 어뷰징 방지 서버 연계 신규 섹션 + 1페이지 개요 + API 계약서 템플릿(Save_StageResult 샘플) + 용어집 + I 기각안 강화 포함.
|
||||
- **이유**: PD님 2026-04-17 일괄 결정 3건으로 초안의 PD-①·② 안건이 소멸. 비재화 보상 관련 설계·안건 전면 제거 후 서버 검증 경계값 수용 구조를 기획팀 솔루션 수령 대비 설계. 인간 개발자 온보딩 단독 사용 가능 완결 문서로 재구성.
|
||||
- **산출물**: `공유/소통/개발팀→PM/2026-04-17_서버개발자_업무지시서_최종본.md` (v1.0). 기존 초안 `공유/소통/완료/2026-04-17_RPT_서버역할_정리_초안.md`로 이동.
|
||||
- **상태**: 완료 (md 최종본). DOCX 변환은 PM `anthropic-skills:docx` 수행 예정.
|
||||
- **기각안**: (1) "초안 그대로 DOCX 변환" — PD 확정 미반영으로 인간 개발자가 낡은 안건 기반 설계 착수 위험, 기각. (2) "비재화 보상 유지(PD-① A/B안 존속)" — PD 직접 결정으로 전제 소멸, 기각. (3) "어뷰징 방지 서버 단독 설계" — PD 직접 결정(기획팀 주도)으로 기각, 서버는 수용 구조만 담당.
|
||||
|
||||
<!-- #PD지시 #개발 #완료 #시뮬레이션 -->
|
||||
## [12:21] Unity MCP 시뮬레이션 방향 전환 기술검토 완료
|
||||
- **요지**: PD #28에 대한 Unity MCP 기반 시뮬레이션 기술검토 수행. 07 Headless 추출안 대비 Unity MCP `execute_code` + EditMode 경로가 결정론·유지비·기획팀 접근성 3축 모두 우위로 판단, 방향 전환 권장.
|
||||
|
|
@ -15,3 +62,52 @@
|
|||
- **산출물**: `공유/소통/기획팀→PM/2026-04-17_Unity_MCP_시뮬레이션_기획검토_기획팀.md`
|
||||
- **상태**: 완료 (검토 보고까지. 실제 Phase 3 재개는 PD님 재개 지시 후)
|
||||
- **기각안**: (a) 기획팀 MCP 도구 직접 호출 — 학습 부담 과대·역할 경계 교란 (b) Python 시뮬 아카이브·삭제 — C6 데이터 보호 위반 + Phase 3 v1 붕괴 원인 분석 근거 소실 리스크
|
||||
|
||||
<!-- #PD지시 #개발 #진행중 #서버역할정리 -->
|
||||
## [세션 시점] 인간 서버 개발자 업무 지시서 초안 작성
|
||||
- **요지**: PD #29 인간 서버 개발자 업무 지시 초안 작성. 서버 관리 데이터 카테고리 8종·클라/서버 경계 매트릭스 6개 액션군·PD 결정 안건 5건 정리. 현 수상한잡화점 `ServerClass.cs`·`ServerInfo.cs` 실측 — PlayFab CloudScript + 로컬 하이브리드 구조 확인.
|
||||
- **이유**: PD 가이드 3종(재화 서버 필수·미션 보상 지급 주체 결정·랭킹 그대로 저장) 기본 적용. 인간 개발자 즉시 착수 가능 수준의 구체 필드·API 예시까지 포함. 메타 시스템 미파악분은 "추가 분석 필요"로 정직 명시.
|
||||
- **산출물**: `공유/소통/개발팀→PM/2026-04-17_RPT_서버역할_정리_초안.md`
|
||||
- **상태**: 진행중 (초안 + PD 결정 안건 제시. PD 결정 수령 후 설계 문서화 단계로 이행)
|
||||
- **기각안**: (a) 서버 처리 최소화·전부 클라 주도 — PD 가이드 1 충돌 (b) 모든 비재화 자산 서버 SOT 전환 — PD 전제 초과·공수 과대 (c) PlayFab 전면 폐기 + 자체 서버 신규 — 서버 Critical 3건 보류 중 범위 확대 리스크 (d) 인간 개발자 배정 전 세부 API 스펙 확정 — 개발자 기술 선호 반영 없는 스펙은 재작업 리스크. **Agent 호출 기각**: `Task` 서브에이전트(서버팀장·클라이언트팀장) 호출이 본 세션 도구 환경 제약으로 불가, 개발팀장 단독 분석으로 대체하되 관점 명시 구분 (C23 정직성)
|
||||
|
||||
<!-- #PD지시 #기획 #완료 #설계 #어뷰징 -->
|
||||
## [세션 시점] 어뷰징 판정 솔루션 기획서 v1 작성 완료
|
||||
- **요지**: PD #32 지시 대응. 시뮬레이터 이론 극값 × 안전마진계수 기반 경계값 도출 방법론 + 2계층(클라/서버) 검증 + F1/F2/F3 3단계 플래그 체계 + 경계값 테이블 JSON 스키마 + 서버 API 호출 흐름까지 설계 원칙·프레임워크 수준으로 정리. 실제 수치는 Unity MCP 시뮬 후 확정(v1은 틀만 제공).
|
||||
- **이유**: 보상 재화 통일 결정에 따라 스테이지 클리어·랭킹·미션에서 클라 입력 검증 체계 필수. 서버 재시뮬 방식은 G-1 기각(자원 낭비·싱글 플레이 구조 오버엔지니어링), 경계값 비교 방식 채택. 재미 우선(C7) — false positive로 정상 플레이어 피해 최소화 위해 벡터별 5~20% 안전마진 설계. P17 ★조건 교차 검증 포함.
|
||||
- **산출물**: `공유/소통/기획팀→PM/2026-04-17_어뷰징판정_솔루션_기획서_v1.md`
|
||||
- **상태**: 완료 (설계·프레임워크 수준. Unity MCP 시뮬 가동·경계값 테이블 v1.0.0 산출은 후속 PD 지시 대기)
|
||||
- **기각안**: (1) 서버 단독 재시뮬 검증 — CPU 비용·난수 동기화·싱글 플레이 오버엔지니어링 (2) ML 기반 이상치 탐지 — 초기 출시 학습 데이터 부재, 장기 보조 수단으로 여지 유지 (3) 클라 단독 판정 — 메모리 조작으로 무력화, 원천 제외 (4) 전체 플레이 로그 전송 — 네트워크·저장소 비용 과도, F3 확정 시 사후 감사용 부분 로그만 서버 재량 (5) 안전 마진 0% 엄격 검증 — false positive 폭발, C7 위반
|
||||
|
||||
<!-- #PD지시 #개발 #완료 #서버참고자료 -->
|
||||
## [16:30] 서버 작업 참고 자료 v1.2 재작성 (외부 서버 작업자용 중립화)
|
||||
- **요지**: v1.1(조직 내부용 서버 개발자 지시서)을 기반으로 외부 작업자용 중립 참고 자료 v1.2 신규 작성. PlayFab 전제 제거(현 사용 중 상태로만 기술)·조직 내부 프로세스 내용 전면 제거·문서 성격 재정의(지시서 → 참고 자료).
|
||||
- **이유**: PD님 직접 지시 — 외부 서버 작업자에게 전달할 때 조직 내부 프로세스 용어(코어룰 참조·PD 지시 번호·결정 대기 안건)가 노출되면 부적절하며, 특정 스택(PlayFab) 강제 전제는 작업자 자율 판단 여지를 박탈. v1.1은 조직 내부용 상세본으로 보존하여 외부·내부 자료 분리.
|
||||
- **산출물**: `공유/소통/개발팀→PM/2026-04-17_서버_작업_참고자료.md` (v1.2, 신규). v1.1 원본 유지.
|
||||
- **상태**: 완료
|
||||
- **기각안**: (1) v1.1 직접 수정 (덮어쓰기) — 조직 내부 상세본 소실 위험, 외부·내부 분리 원칙 위반. 신규 파일 분리 채택. (2) 서버 스택 선택을 v1.2에서 확정 제시 — 외부 작업자 자율 판단 박탈, '열린 결정 사항'으로 중립 유지 채택. (3) 결정 대기 2건(PD-③·PD-④)을 각주로 축약 유지 — 조직 내부 미결 안건의 외부 노출 부적절, 전면 삭제 채택. (4) 기각안 섹션을 외부 자료에도 포함 — P24는 내부 규칙, 외부 자료 성격과 불일치. 대화로그(본 엔트리)에만 기록 채택.
|
||||
|
||||
<!-- #PD지시 #개발 #완료 #Tier1 -->
|
||||
## [세션 시점] Tier 1 잔여 9종 구현 완료 + 로그 경로 정규화 (PD 지시 #1·#5-A, PM 일괄 승인)
|
||||
- **요지**: 개발팀장이 `코어코드/NerdNavis.Framework/` Tier 1 잔여 Attribute 3종 + Util 6종 구현 + 각 모듈 단위 테스트 추가 + CHANGELOG 갱신. PD 지시 로그 #1·#5 산출물 경로 정규화(구 경로·커밋 해시·glob 제거)로 `verify_log_paths.sh` 감사 15건 전수 실존 확인 통과.
|
||||
- **이유**: PD님 2026-04-17 마무리 지시로 팀장 재량 진행 가능 작업 일괄 승인. 차단 요인 없음. OI-2 C+H1 승인 완료·Phase 3 재개 대기 등과 무관한 순수 구현 영역.
|
||||
- **산출물**: `코어코드/NerdNavis.Framework/Runtime/Core/Attribute/` (ReadOnlyAttribute/ShowIfAttribute/ArrayTitleAttribute 3종) + `코어코드/NerdNavis.Framework/Runtime/Core/Util/` (EnumToInt/EnumEx/FormatEx/MathEx/KeyMaker/ValidationEx 6종) + `코어코드/NerdNavis.Framework/Tests/Runtime/Core/` (Attribute/Util 테스트 9종) + `CHANGELOG.md` 갱신 + `공유/PD_지시_트래킹/개발팀_PD_지시_로그.md` #1·#5 경로 정규화.
|
||||
- **상태**: 완료
|
||||
- **기각안**: (1) 전체 Tier 1 16종 중 미구현 12종 일괄 구현 — Data/Event/Container 영역은 MonoSingleton·ServiceLocator와의 상호작용 설계 재검증 필요. 단일 응답에서 품질 보장 가능한 Attribute 3종 + Util 6종 = 9종 범위로 한정 채택. (2) 박싱 회피를 `Convert.ChangeType` 캐시로 우회 — 여전히 힙 할당 발생. `System.Runtime.CompilerServices.Unsafe.As<,>` 기반 근본 해결 채택(EnumToInt). (3) `KeyMaker` 구분자로 `'_'` 유지 — 수상한잡화점에서 `_`와 `:` 혼재로 조회 실패 경험. 프레임워크 표준 `:` 고정 채택. (4) 각 Util에 UnityEngine 참조 허용 — 서버/배치 컨텍스트 재사용 불가. 순수 BCL 의존만 채택(C11 범용성).
|
||||
|
||||
<!-- #PD지시 #개발 #완료 #UI #메타 -->
|
||||
## [세션 시점] Phase 0-B 최종 완결 — UI 아키텍처(11) + 메타시스템(12) 문서 신설
|
||||
- **요지**: 수상한잡화점 파악 잔여 40% 중 UI·메타 영역을 `11_UI아키텍처_v1.md` + `12_메타시스템_v1.md`로 문서화. Assets 전수 `ls` + 키워드 `find` 실측 기반. 프레임워크 흡수 계획(Tier 1 UI 컴포넌트 + Tier 2 Save/Economy) 구체화.
|
||||
- **이유**: PD님 2026-04-17 마무리 지시. 08(전투)·09(카드)·10(데이터) 완결 후 Phase 0-B를 UI·메타까지 확장. 헌법 제1원칙 목표 2(인사이트 기록 → 차기 프로젝트 참고)에 직접 기여 — 현 프로젝트 약점(*.Info 3역할·세이브 버전 관리 부재·재화 하드코딩) 차기 개선 안건 §10 기록.
|
||||
- **산출물**: `프로젝트/수상한잡화점/개발/11_UI아키텍처_v1.md` · `프로젝트/수상한잡화점/개발/12_메타시스템_v1.md`.
|
||||
- **상태**: 완료
|
||||
- **기각안**: (1) UGUI 19+19 스크립트 public API 메서드 전수 목록화 — 토큰 비용 과다, 구조·영향도 분류 목적에 불필요. 클러스터/LOC/범용성 3축 요약 채택. (2) UIToolkit 병행 매핑 문서화 — 기획 방향 UGUI 단일, 차기 프로젝트 R&D로 이관. (3) 메타시스템 보안 취약점 감사 — `05_서버연동_현황_v1.md` Critical 3건(#2 보류)과 중복. 본 문서는 구조·흡수 계획에 집중. (4) *.Info 클래스 필드별 세이브 대상 분류표 — 토큰 비용 + 프레임워크 흡수 대비 정보량 과다. 차기 Phase에 감사로 분리.
|
||||
|
||||
<!-- #PD지시 #개발 #완료 #QP #시뮬레이터 -->
|
||||
## [세션 시점] Phase 0-C Q-P1/Q-P2 응답서 + 시뮬레이터 전략 v2 (PD 지시 #5-B·#28)
|
||||
- **요지**: 기획팀 Q-P1(4마리 노드 초반 위험도 의도성) + Q-P2(터치 방어 메커닉 3항)에 대한 개발 관점 응답서 발행. PD님 #28 Unity MCP 전환 반영하여 시뮬레이터 전략 v2로 재설정.
|
||||
- **이유**: #5-B Phase 0-C 잔여 작업. 단, Q-P1은 기획 의도 영역이므로 **단독 답변 불가** 명시(C11 구분) + Q-P2는 초벌 스캔(`PCActor.cs` L37 실측)으로 50% 수치·쿨다운 정밀 수치는 2차 응답서로 분리. 시뮬레이터는 Python 폐기·Unity MCP 단일축 재확정 + 인프라 4종 설계 포인트 제시.
|
||||
- **산출물**: `공유/소통/개발팀→PM/2026-04-17_Phase0-C_QP_응답서_개발팀.md`.
|
||||
- **상태**: 완료 (Q-P2 2차 정밀 응답은 후속)
|
||||
- **기각안**: (1) Q-P1에 "의도된 설계로 추정" 단정 응답 — 개발팀 의사결정 권한 외, C23 위반. 의도 선언은 기획팀으로 환송. (2) Q-P2 3항 전수 리버스 엔지니어링을 본 응답서에 포함 — 작업량 막대 + 범위 초과. 2차 응답서로 분리. (3) Python 시뮬레이터 복구 시도 — PD님 #28 "폐기 사안" 정면 위반. (4) Unity 외 3rd-party 시뮬레이터 검토 — PD님 Unity MCP 단일 방향 확정, 검토 대상 아님.
|
||||
|
||||
|
|
|
|||
|
|
@ -236,3 +236,28 @@
|
|||
- **산출물**: SKILL.md P24 엔트리 표준 형식 표 "기각안" 필드 "선택 (결정 시 권장)" → "**결정·설계 엔트리 필수**" 개정 + "기각안 필드 필수화" 근거 섹션 신설 (범위·근거·적용 주체·기입 방법·발의 출처)
|
||||
- **적용 주체**: PM·기획팀장·개발팀장·전문 에이전트(balance/content/level/narrative/system/ux) + 3축 감사관(pm/dev/plan-auditor) 공통
|
||||
- **상태**: 완료
|
||||
|
||||
<!-- #PD지시 #기획 #완료 #기록의무 -->
|
||||
## [기획팀장] 팀장 재량 진행 3건 일괄 완료 (PD 지시 #33·#34·#35)
|
||||
- **요지**: PD님 "팀장 재량 진행 가능 작업 전건 일괄 승인, 마무리 지시" 수령. 3건 즉시 착수·완료
|
||||
1. 밸런스 요구서 표준 템플릿 신설 → `공유/소통/기획팀→개발팀/REQ-템플릿_밸런스수치.md` (9개 섹션)
|
||||
2. 전문가 에이전트 6종 기록 의무 명시 + 구 P20 잔존 제거 → balance/content/level/narrative/system/ux-designer.md 6종 "기록 의무 (영역 특화)" 섹션 신설
|
||||
3. 밸런싱 md 4종 변경 이력 테이블 표준화 → 스테이지난이도곡선·밸런싱전략·전체테이블감사·빌드_조건_충돌점검 v1.md 하단 P16 추적성 테이블 append
|
||||
- **이유**: 조직 공유·기록 체계 일관성(P27) 강화 + 헌법 제1원칙 목표 2 원칙 B(인사이트 축적) 이행. 기획팀 산출물·개발팀 협업 요청이 단일 포맷·단일 경로로 집약되면 PM 교차 검증과 차기 프로젝트 참고 자료화 모두 용이
|
||||
- **기각안**:
|
||||
1. REQ 템플릿 "개발 관점 우려 예상" 섹션(C11 사전 제시) 제외 — 기획팀이 개발 영역 예측을 하는 것이 월권 소지 있으나, 명시적으로 "기획팀 예측·개발팀이 추가 발견 시 C3 제기" 구조로 명시하면 논의 효율 상승. 채택
|
||||
2. 에이전트 6종 기록 의무를 각 파일에 중복 기재 대신 SKILL.md로만 일원화 — C14-4 참조 무결성 관점에서 매력적이나, 전문 에이전트가 자기 역할 맥락(영역 특화 원칙)과 함께 로드되는 것이 체감 준수율 상승. 영역 특화 부분은 각 파일 유지, 공통 의무는 참조형으로 축소 채택
|
||||
3. 밸런싱 md 변경 이력 테이블을 `공유/대화로그/`로 일원화 — 추적성은 대화로그에 이미 있으나, 수치 문서 내 인라인 기록이 편집 시점에 즉시 눈에 띄어 누락 위험이 낮음. 현행 유지(문서 내 인라인)
|
||||
- **산출물**:
|
||||
- REQ 템플릿: `공유/소통/기획팀→개발팀/REQ-템플릿_밸런스수치.md`
|
||||
- 에이전트 6종: `.claude/agents/{balance,content,level,narrative,system,ux}-designer.md`
|
||||
- 밸런싱 md 4종: `프로젝트/수상한잡화점/기획/{스테이지난이도곡선,밸런싱전략,전체테이블감사,빌드_조건_충돌점검}_v1.md`
|
||||
- **상태**: 완료
|
||||
|
||||
<!-- #자율작업 #PM #완료 #plan-auditor모드B -->
|
||||
## [PM] plan-auditor 모드 B 감사 — 재량작업 3건 일괄 완료 정합성 검증
|
||||
- **요지**: 기획팀장 #33·#34·#35 완료 후 plan-auditor 모드 B 호출. Critical 0·Major 1(수상한잡화점 대화로그 누락)·Minor 1·Improvement 2 도출
|
||||
- **이유**: P27-1 3축 감사 체계 신설 후 첫 plan-auditor 호출. 재량 작업 일괄 진행 시에도 프로젝트 축 대화로그 SOT 경계가 유지되는지 검증
|
||||
- **기각안**: 모드 A(응답 발신 직전) 호출 — 기획팀장이 이미 종료되어 사후 교차 검증이 필요, 모드 B 적합
|
||||
- **산출물**: `공유/소통/plan-auditor→PM/2026-04-17_감사보고_재량작업_일괄완료.md`
|
||||
- **상태**: 완료
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -0,0 +1,125 @@
|
|||
---
|
||||
type: 응답서
|
||||
from: 개발팀장
|
||||
to: 총괄PM / 기획팀장
|
||||
subject: Phase 0-C Q-P1/Q-P2 개발 관점 응답 + 시뮬레이터 전략 업데이트
|
||||
date: 2026-04-17
|
||||
status: 완료
|
||||
related:
|
||||
- 프로젝트/수상한잡화점/기획/Phase0_1_앵커전투시뮬레이션_v1.md
|
||||
- 프로젝트/수상한잡화점/개발/07_시뮬레이터_이원화_해소_착수계획_v1.md
|
||||
- 프로젝트/수상한잡화점/개발/08_전투시스템_SOT_v1.md
|
||||
depends_on:
|
||||
- PD 지시 #5-B (Phase 0-C Q-P 응답)
|
||||
- PD 지시 #28 (Unity MCP 전환)
|
||||
---
|
||||
|
||||
# Phase 0-C — Q-P1/Q-P2 응답 + 시뮬레이터 전략 업데이트
|
||||
|
||||
## 0. 본 문서 범위
|
||||
|
||||
기획팀이 `Phase0_1_앵커전투시뮬레이션_v1.md §6` 에 남긴 질의 2건에 대한 개발 관점 응답 + **2026-04-17 PD님 Unity MCP 전환 지시(#28)를 반영한 시뮬레이터 전략 재설정**.
|
||||
|
||||
개발팀 PD 지시 로그 #5-B의 "Q-P1/P2/P3" 표기는 실제로 Q-P1(질의 1건) + Q-P2(서브 질의 3건)의 편의 표기였음. 본 응답서는 기획팀 원문 기준으로 Q-P1·Q-P2를 대응한다.
|
||||
|
||||
## 1. Q-P1 응답 — "4마리 노드 초반 위험도는 의도된 설계인가?"
|
||||
|
||||
### 1-1. 기획팀 질의 요지
|
||||
> "카드 0장 상태에서 4마리 노드가 '터치 방어 없이는 위험'한 현재 상태는 의도된 설계? (=터치 방어를 자연스럽게 가르치기 위함?)"
|
||||
|
||||
### 1-2. 개발팀 응답 (실증 기반)
|
||||
**개발팀 단독 답변 불가 범주**. 이유:
|
||||
1. "의도된 설계 여부"는 **기획 의도의 영역**이며 개발팀은 결정권이 없음 (C7·C11 구분)
|
||||
2. 개발팀이 제공할 수 있는 것은 **코드 실측**(터치 방어 유무에 따른 생존 시나리오) + **구조적 힌트**(초기 4마리 노드가 카드 풀 구성 규칙과 맞물려 있는지)
|
||||
|
||||
### 1-3. 개발팀이 제공 가능한 근거
|
||||
- `Assets/Script/InGame/Actor/PCActor.cs` L37: `eActorStatus.Defecne` + `InGameInfo.Ins.Set_PCBlock` → 터치 방어는 PC(플레이어 캐릭터) 상태 효과로 실체화되어 있음
|
||||
- 카드 0장 상태의 실측 DPS·EHP는 개발팀 Unity MCP 시뮬레이션으로 제공 가능 (§3 전략 참조)
|
||||
|
||||
### 1-4. 권장 진행 방향
|
||||
- 기획팀이 **의도 명확화 선언** → 개발팀이 Unity MCP로 해당 설계가 수치적으로 성립하는지 검증
|
||||
- 구체적으로: 기획팀이 "터치 방어 없이 4마리 처리 가능 여부"의 목표 비율(예: 80% 생존)을 수치화 → 개발팀이 시뮬레이션으로 현 수치가 목표를 만족하는지 응답
|
||||
|
||||
## 2. Q-P2 응답 — "터치 방어 메커닉 정확도"
|
||||
|
||||
### 2-1. 기획팀 질의 요지
|
||||
1. 터치 1회 = 다음 공격 1회 피해 50% 감소?
|
||||
2. 터치 유지 중 계속 50% 감소?
|
||||
3. 쿨다운?
|
||||
|
||||
### 2-2. 개발팀 응답 (코드 실증 기반, 미확인 항목 명시)
|
||||
|
||||
**확인된 사실** (실제 `PCActor.cs`·`Actor.cs` 실측):
|
||||
- 터치 방어는 `eActorStatus.Defecne`(원문 오타 `Defecne`) 상태 효과로 모델링
|
||||
- `InGameInfo.Ins.Set_PCBlock` 을 통해 블록 스프라이트·상태를 설정
|
||||
- "블록(Block)" 용어와 "방어(Defense)" 용어가 **혼용**되어 있음 (현 구조의 약점)
|
||||
|
||||
**미확인 — 정밀 코드 리뷰 필요**:
|
||||
- 50% 감소 수치의 실제 테이블 위치 (Actor·Effect·Card 중 어디에 기록?)
|
||||
- 감소 비율이 **고정값인지 카드·장비·인장 옵션에 따라 변동**하는지
|
||||
- 쿨다운 존재 여부·값
|
||||
- "1회성"인지 "지속형"인지 (상태 효과 duration 필드가 별도 관리되는지)
|
||||
|
||||
**개발팀 차기 조치** (본 응답서 발신 후 즉시 착수 가능):
|
||||
1. `Actor.cs`·`PCActor.cs`·관련 `Effect*.cs` 정밀 리뷰하여 수치 위치 식별
|
||||
2. 기획팀 `StatusEffect` 마스터 테이블 확인
|
||||
3. Q-P2 3문항 각각에 **코드·테이블 실측 근거 포함 2차 응답서** 제출
|
||||
|
||||
### 2-3. 본 응답의 한계
|
||||
본 응답은 **초벌 스캔 결과**이며, 50% 감소·쿨다운 확정 수치는 미확정. Q-P2는 **2차 응답서로 완결** 필요 (C23 정직 보고).
|
||||
|
||||
## 3. 시뮬레이터 전략 업데이트 (07 문서 후속)
|
||||
|
||||
### 3-1. 배경 변경
|
||||
- **PD님 2026-04-17 직접 지시 (#28)**: 기획팀 시뮬레이션 작업은 **Unity MCP 기반**으로 전환
|
||||
- Python 시뮬 파일은 **소실 확정 — 폐기 사안**
|
||||
- 교차 검증 축 이원화 방침 폐기 → **Unity MCP 단일 축**으로 확정
|
||||
|
||||
### 3-2. 재설정된 시뮬레이션 전략
|
||||
|
||||
| 항목 | 결정 |
|
||||
|------|------|
|
||||
| 시뮬레이션 실행 환경 | Unity Editor + Unity MCP (`mcp__unity-mcp__*`) |
|
||||
| 결과 취득 방법 | `execute_code`·`run_tests`·`manage_editor` 등으로 플레이 모드 제어 + 로그/결과 수집 |
|
||||
| 시뮬레이션 코드 위치 | `Assets/Script/Server/`·`Assets/Script/InGame/` 등 기존 프로젝트 내부 (별도 시뮬레이터 레포 불필요) |
|
||||
| 성능 모드 | Headless 불필요 — 기획팀 밸런싱은 시각적 피드백 포함이 유리. 배치 검증 시에만 PlayMode 자동화 |
|
||||
| 결과 저장 | `기획팀/.cache/` 또는 `공유/시뮬결과/` (기획팀 결정) — 개발팀은 실행 환경 제공까지 |
|
||||
|
||||
### 3-3. 개발팀이 제공할 인프라 (Phase 0-C 잔여)
|
||||
1. **시뮬레이션 진입점 스크립트**: 전투만 격리 실행 가능한 `SimulationRunner.cs` 프로토타입 (PlayMode 의존 최소화)
|
||||
2. **파라미터 외부화**: 앵커 전투 시나리오(몬스터 구성·PC 상태·카드 0장 등)를 JSON/ScriptableObject 외부 입력으로 받도록 리팩토링 포인트 식별
|
||||
3. **결과 수집 포맷**: 시뮬레이션 1회 결과를 `{timestamp, scenario_id, turns, dmg_taken, survived, ...}` 구조화 JSON 출력
|
||||
4. **Unity MCP 호출 템플릿**: 기획팀장이 `Task` 또는 `/게임플레이` 에이전트 호출 시 바로 쓸 수 있는 MCP 호출 스니펫 (3종: 단일 실행 / 파라미터 스윕 / 배치 비교)
|
||||
|
||||
**작성 예정 산출물** (본 응답서 뒤 착수):
|
||||
- `공유/소통/개발팀→PM/2026-04-17_Unity_MCP_시뮬레이션_인프라_설계_v2.md` — 상기 4종 인프라의 구체 설계
|
||||
- 기존 `공유/소통/개발팀→PM/2026-04-17_Unity_MCP_시뮬레이션_기술검토_개발팀.md` (이미 존재, #28 참조) 위에 **실행 인프라 단계** 추가
|
||||
|
||||
### 3-4. 기획팀 작업 재개 조건
|
||||
- 위 3-3의 인프라 4종 중 최소 (1)·(3)·(4)가 완료되면 기획팀이 앵커 전투 시뮬레이션 작업 재개 가능
|
||||
- (2) 파라미터 외부화는 병행 진행 가능 (선택사항)
|
||||
|
||||
## 4. 기각안 (P24)
|
||||
|
||||
- **기각안 A: 전투 메커닉 전수 자동 리버스 엔지니어링 + 본 응답서에 통합 수록** — 작업량 막대 + 본 응답서 범위 초과. Q-P2 2차 응답서로 분리
|
||||
- **기각안 B: Python 시뮬레이터 복구 시도** — PD님 "폐기 사안" 확정(#28 비고란). 복구 시도 자체가 지시 위반
|
||||
- **기각안 C: Unity 외 3rd-party 시뮬레이터 도입 검토** — PD님 Unity MCP 단일 방향 확정. 검토 대상 아님
|
||||
- **기각안 D: Q-P1에 "의도된 설계로 추정됨" 단정 응답** — 개발팀 의사결정 권한 외. C23 위반 회피
|
||||
|
||||
## 5. 후속 작업 · 블로커
|
||||
|
||||
### 5-1. 개발팀 즉시 착수 항목 (본 응답서 발신 후)
|
||||
1. Q-P2 정밀 2차 응답 (코드·테이블 실측)
|
||||
2. §3-3 인프라 4종 (1)·(3)·(4) 구현
|
||||
3. Unity MCP 호출 스니펫 문서화
|
||||
|
||||
### 5-2. 기획팀 필요 액션
|
||||
1. Q-P1에 대한 **기획 의도 선언** (의도된 설계? 개선 필요?)
|
||||
2. §3-3 시뮬레이션 결과 저장 위치 결정
|
||||
3. Q-P2 정밀 응답 수령 후 터치 방어 정책 재확인
|
||||
|
||||
### 5-3. PM 조율 필요
|
||||
- 없음 (팀 자율 진행 가능 범위, C29-1 정합)
|
||||
|
||||
## 부록. 변경 이력
|
||||
- **v1 (2026-04-17)**: 초판. 개발팀장 Phase 0-C Q-P 응답 + Unity MCP 전환 반영 시뮬레이터 전략 v2 초안 작성.
|
||||
|
|
@ -0,0 +1,186 @@
|
|||
---
|
||||
from: 개발팀장
|
||||
to: PM (DOCX 변환 → 외부 서버 작업자)
|
||||
date: 2026-04-17
|
||||
type: 참고자료
|
||||
status: 완료
|
||||
version: v1.2
|
||||
audience: 외부 서버 작업자
|
||||
tags: [서버참고자료, 수상한잡화점, 서버경계, 데이터SOT]
|
||||
---
|
||||
|
||||
# 서버 작업 참고 자료 — 수상한잡화점
|
||||
|
||||
> **본 문서의 성격**: 수상한잡화점 프로젝트 서버 파트 작업자에게 전달하는 **참고 자료**입니다. 공식 업무 지시서가 아니라, 현 프로젝트의 서버 현황·데이터 카테고리·클라-서버 경계 원칙·참고용 API 샘플을 정리한 배경 문서입니다. 세부 구현 방향과 서버 스택 선택은 작업자가 판단·제안할 수 있는 열린 영역으로 남겨져 있습니다.
|
||||
> **권장 완독 시간**: 5~7분
|
||||
|
||||
---
|
||||
|
||||
## 0. 1페이지 개요
|
||||
|
||||
- **프로젝트**: 수상한잡화점 (로그라이크 RPG, Unity 2022+, 모바일)
|
||||
- **현 서버 스택**: PlayFab 사용 중 (Title API·CloudScript·Leaderboard·Profiles·Mail). 유지·전환·하이브리드 여부는 열린 결정 사항
|
||||
- **서버 담당 영역 (현 프로젝트 범위)**: ① 재화 SOT 서버 검증·지급 ② 랭킹 저장·조회 ③ 우편·IAP 영수증 검증 ④ 서버 시간 기준 리셋
|
||||
- **어뷰징 판정 책임**: 클라이언트 주도. 서버는 판정 결과(플래그) 수신 시 지급 거부 처리만 수행 (섹션 5 참조)
|
||||
- **현 상태**: 서버 Critical 보안 3건 보류 중, 서버 작업자 합류 시점 재개 예정
|
||||
|
||||
**프로젝트 기본 원칙 4종 (확정, 변경 불가)**
|
||||
1. 모든 보상은 **재화 형태로 지급**. 비재화 보상(칭호·스킨·장비·PC 등) 없음 → 모든 보상은 서버 검증·지급 대상
|
||||
2. 재화 사용·획득은 **항상 서버 응답 필수**. 클라 단독 처리 금지
|
||||
3. 미션 클리어 판단은 **클라 재량**. 서버는 보상 지급 시점에만 개입
|
||||
4. 랭킹 등록은 **클라 정보를 서버가 그대로 저장**
|
||||
|
||||
---
|
||||
|
||||
## 1. 현 서버 스택 현황 (PlayFab 사용 중)
|
||||
|
||||
| 구성 요소 | 현 상태 | 비고 |
|
||||
|----------|--------|------|
|
||||
| PlayFab 인증·UserData·TitleData | 사용 중 | - |
|
||||
| CloudScript | 사용 중 (`Get_UserInfo`·`Get_MailList`·`Get_MailReward` 등) | 함수 dump 후 버전 관리 편입 필요 |
|
||||
| Leaderboard (Progression API) | 읽기 경로 존재 (`GetLeaderboard`·`GetLeaderboardAroundEntity`) | 등록 경로 미확인 → 재설계 필요 |
|
||||
| Mail | `Get_AllMail` CloudScript 존재 | 재화 보상 우편 처리 |
|
||||
| `Save_StageResult` (스테이지 완료 처리) | 비활성 상태 (주석 처리) | 복원·신규 설계 필요 |
|
||||
| `InappInfo.cs` (IAP 영수증) | 비활성 상태 | 재활성화 + `ValidateGooglePlayPurchase` 연결 |
|
||||
| `Server_Live_ID`·`Server_Dev_ID` | 코드 주석 처리 상태 | 인수인계 시 복구 |
|
||||
|
||||
> **스택 선택은 열린 사안**: 본 섹션은 "현재 이렇게 구현되어 있다"는 현황 기술입니다. PlayFab 유지, 하이브리드 구성, 별도 스택으로의 전환 등 서버 아키텍처 방향은 작업자 판단·제안 가능 영역입니다.
|
||||
|
||||
---
|
||||
|
||||
## 2. 서버 관리 데이터 카테고리 (8개 도메인)
|
||||
|
||||
| # | 도메인 | 주요 필드 | SOT 방향 |
|
||||
|---|--------|----------|---------|
|
||||
| 1 | 재화 인벤토리 | `dic_Item[itemid] = amount` (Gold·Soul 등) | 서버 SOT 필수 |
|
||||
| 2 | PC(캐릭터) 보유 | `dic_PC[pcid]` — 레벨·경험치·각성 | 서버 SOT |
|
||||
| 3 | 장비·카드 | `Equipment`·`dic_Card[cardid]` | 서버 SOT |
|
||||
| 4 | 스테이지 진행 | `StageData.dic_stagedata[diff][chapter][stageid].Star` | 서버 SOT (보상 지급 연결) |
|
||||
| 5 | 미션·출석 | `Mission.dic_Mission[type]`·`RewardAttandanceDay` | 서버 SOT (재화 보상) |
|
||||
| 6 | 랭킹 | Leaderboard (현 PlayFab) | 서버 저장 |
|
||||
| 7 | 우편 | CloudScript 기반 (현 PlayFab) | 서버 SOT |
|
||||
| 8 | 시즌·패스 | 신규 정의 필요 | 기획 SOT 정의 후 구체화 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 클라/서버 경계 — 핵심 원칙 3
|
||||
|
||||
1. **재화 사용·획득 → 서버 응답 필수**
|
||||
- 클라: 사용·획득 요청 전송 / 서버: 잔고 검증·차감·응답 (또는 지급·응답)
|
||||
- 서버 거부 시 클라 롤백
|
||||
|
||||
2. **미션 클리어 → 클라 판단, 재화 보상은 서버 지급**
|
||||
- 클라: 조건 체크·카운트 누적·달성 판정 / 서버: 보상 수령 요청 수신 시 지급
|
||||
|
||||
3. **랭킹 등록 → 클라가 계산한 값을 서버가 그대로 저장**
|
||||
- 클라: 점수·메타정보 계산·전송 / 서버: Leaderboard 저장
|
||||
|
||||
**보상 통일**: 모든 보상은 재화 단위. 비재화 보상(칭호·스킨·장비·PC 등) 없음.
|
||||
|
||||
---
|
||||
|
||||
## 4. 핵심 API 참고 예시
|
||||
|
||||
> 아래는 **현 구현 참고용** 샘플입니다. 서버 스택·구현 방식이 변경될 경우 인터페이스는 재설계 대상입니다. "반드시 이 방식을 유지해야 한다"는 의미가 아닙니다.
|
||||
|
||||
### 4-1. `Save_StageResult` (복원 필요 — 현 PlayFab CloudScript 기준 샘플)
|
||||
|
||||
**유형**: CloudScript 함수 / **호출 권한**: Client (LoginSession 유효)
|
||||
**역할**: 스테이지 클리어 결과를 저장하고 재화를 지급
|
||||
|
||||
**요청 파라미터** (예시):
|
||||
```json
|
||||
{
|
||||
"difficulty": 2,
|
||||
"chapter": 3,
|
||||
"stageId": 47,
|
||||
"starCount": 3,
|
||||
"clearTimeMs": 125000,
|
||||
"droppedItems": [{"itemId": 101, "amount": 500}],
|
||||
"is_abuse_flag": false
|
||||
}
|
||||
```
|
||||
|
||||
**응답 (성공)**:
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"grantedRewards": [{"itemId": 101, "amount": 500}],
|
||||
"newStar": 3,
|
||||
"stageProgress": {"maxStageId": 47}
|
||||
}
|
||||
```
|
||||
|
||||
**응답 (실패)**:
|
||||
| 에러 코드 | 조건 | 클라 처리 |
|
||||
|----------|------|----------|
|
||||
| `AbuseFlagged` | `is_abuse_flag: true` 수신 | 지급 거부, 기록 저장 (섹션 5) |
|
||||
| `SessionInvalid` | 세션 없음·만료 | 재로그인 유도 |
|
||||
| `StageLocked` | 해당 스테이지 미해금 | 경로 이탈 처리 |
|
||||
|
||||
### 4-2. `Grant_MissionReward`
|
||||
- 미션 보상 재화 지급. 클라가 미션 완료 판정 후 수령 요청 시 서버가 지급·응답
|
||||
- `is_abuse_flag` 동일 수용 (클라가 자체 판정 시 true 동봉 가능)
|
||||
|
||||
### 4-3. 랭킹 등록 경로 (신규 설계 필요)
|
||||
- 현 코드에서 등록 호출처 미확인 → 재구축 필요. 엔드포인트명 예: `Submit_RankingEntry`
|
||||
- 클라 전송 점수·메타정보 그대로 Leaderboard 저장. 세부는 설계 단계에서 확정
|
||||
|
||||
---
|
||||
|
||||
## 5. 참고: 어뷰징 방지 체계 (클라 주도, 서버 최소 역할)
|
||||
|
||||
> **프로젝트 정책**: 어뷰징 **판정은 클라이언트 책임**. 서버는 판정 결과(플래그)만 받아 처리합니다.
|
||||
|
||||
**판정 책임**: 클라이언트 (경계값 보관·수치 검증 전부 클라에서 수행)
|
||||
|
||||
**서버 역할 (최소)**:
|
||||
- 재화 지급 요청 페이로드에 **`is_abuse_flag: true`** 가 포함된 경우, 해당 지급을 **거부**하고 **기록** 저장
|
||||
- 플래그 저장 위치: 현 스택 기준 Profile 필드 또는 별도 모니터링 엔드포인트 (구현 단계에서 결정)
|
||||
- 관리자 알림 채널(Slack Webhook·이메일 등) 연계는 구현 단계에서 결정
|
||||
|
||||
**서버가 하지 않는 것 (프로젝트 정책)**:
|
||||
- 경계값 테이블 보관 (Title Data 등에 적재 불필요)
|
||||
- 경계값과 클라 전송 수치 비교 검증
|
||||
- 어뷰징 판정 로직 자체
|
||||
|
||||
**협의 범위**: 클라이언트팀 구현 시 필요하면 서버 작업자와 **플래그 인터페이스 스펙**만 협의합니다 (판정 로직 자체는 협의 대상 아님).
|
||||
|
||||
---
|
||||
|
||||
## 6. 환경 셋업 체크리스트
|
||||
|
||||
- [ ] 현 서버 스택(PlayFab) 접근 권한 수령 (Title ID 인수·Game Manager 대시보드·CloudScript revision 접근) — 현 스택 유지 시
|
||||
- [ ] 현 배포 CloudScript 전문 dump → 버전 관리 편입 (경로 후보: `코어코드/수상한잡화점_서버/`)
|
||||
- [ ] Unity 프로젝트 clone + `git fetch origin && git pull`
|
||||
- [ ] Unity 에디터 설치 (프로젝트 버전 기준)
|
||||
- [ ] 현 PlayFab SDK 연동 확인 (빌드·실행 1회 성공)
|
||||
- [ ] IDE (VS Code 또는 JetBrains Rider) + Node.js (CloudScript 로컬 테스트용)
|
||||
|
||||
---
|
||||
|
||||
## 7. 용어집
|
||||
|
||||
| 용어 | 뜻 |
|
||||
|------|---|
|
||||
| PlayFab | Microsoft BaaS. 본 프로젝트가 현재 사용 중인 서버 스택 |
|
||||
| CloudScript | PlayFab 서버 사이드 JavaScript 실행 환경 |
|
||||
| Title Data | PlayFab Title 단위 공용 설정 저장소 |
|
||||
| UserData | PlayFab 유저별 서버 저장소 |
|
||||
| Leaderboard | PlayFab 순위표 서비스 (Progression API) |
|
||||
| ObscuredType | `CodeStage.AntiCheat` 로컬 변조 방어 타입. 서버 검증 대체재는 아님 |
|
||||
| ServerData | 수상한잡화점 프로젝트 클래스(`ServerClass.cs`). 유저 데이터 SOT 로컬 표현 |
|
||||
| SOT | Source of Truth. 데이터의 단일 진실 근원 |
|
||||
| AntiCheat | 어뷰징·치트 방지 체계. 본 프로젝트는 클라 판정 + 서버는 플래그 수신 시 지급 거부 |
|
||||
|
||||
---
|
||||
|
||||
## 8. 변경 이력
|
||||
|
||||
| 일시 | 버전 | 요지 |
|
||||
|------|------|------|
|
||||
| 2026-04-17 | v1.2 | 외부 서버 작업자용 참고 자료로 중립화 — PlayFab 전제 제거(현 사용 중 상태로만 기술), 조직 내부 프로세스 표현 제거, "지시서" → "참고 자료"로 성격 재정의 |
|
||||
|
||||
---
|
||||
|
||||
**용도**: 서버 작업자 합류 시 본 자료를 출발점으로 사용. 세부 서버 스택 선택, 아키텍처 방향, API 스펙 확정은 배정 후 설계 단계에서 진행.
|
||||
|
|
@ -0,0 +1,207 @@
|
|||
---
|
||||
from: 개발팀장
|
||||
to: PM (DOCX 변환 → 인간 서버 개발자)
|
||||
date: 2026-04-17
|
||||
type: 지시서
|
||||
status: 완료
|
||||
version: v1.1
|
||||
tags: [서버개발자지시서, 요약판, 서버역할, 클라서버경계, PlayFab, 수상한잡화점]
|
||||
related: [C8, C11, C28, C29, C30, P13, P14, P18]
|
||||
depends_on: [PD-#2, PD-#30, PD-#신규_어뷰징책임_재확정]
|
||||
supersedes: v1.0 (2026-04-17 동일 경로, PD님 어뷰징 책임 재확정·요약판 재작성 지시로 대체)
|
||||
---
|
||||
|
||||
# 인간 서버 개발자 업무 지시서 (요약판 v1.1)
|
||||
|
||||
> **대상**: 인간 서버 개발자 (수상한잡화점 서버 파트 합류 예정)
|
||||
> **읽는 시간**: 5~7분 완독 가능 수준으로 축약
|
||||
> **v1.1 변경 요지**: (1) 어뷰징 판정 **클라 100% 책임**으로 확정 (PD님 재결정). 서버는 클라가 보내준 **판정 플래그만 수신**하여 지급 거부. (2) v1.0 446줄을 요약판으로 전면 재작성. 세부 원문은 초안 `공유/소통/완료/2026-04-17_RPT_서버역할_정리_초안.md` 참조.
|
||||
|
||||
---
|
||||
|
||||
## 0. 1페이지 개요
|
||||
|
||||
- **프로젝트**: 수상한잡화점 (로그라이크 RPG, Unity 2022+, 모바일)
|
||||
- **서버 스택**: PlayFab 기반 (Title API·CloudScript·Leaderboard·Profiles·Mail) — 본 지시서는 PlayFab 유지 전제
|
||||
- **역할 범위**: ① 재화 SOT 서버 검증·지급 ② 랭킹 저장·조회 ③ 우편·IAP 영수증 검증 ④ 서버 시간 기준 리셋
|
||||
- **어뷰징 판정**: **클라 100% 책임**. 서버는 판정 결과(플래그) 수신 시 지급 거부만 수행 (섹션 5 참조)
|
||||
- **현 상태**: 서버 Critical 3건 보류 중(PD 지시 로그 #2), 인간 개발자 배정 후 재개
|
||||
|
||||
**기본 원칙 4종 (PD 확정, 변경 불가)**
|
||||
1. 모든 보상은 **재화 형태로 지급**. 비재화 보상 없음 → 모든 보상은 서버 검증·지급 대상
|
||||
2. 재화 사용·획득은 **항상 서버 응답 필수**. 클라 단독 처리 금지
|
||||
3. 미션 클리어 판단은 **클라 재량**. 서버는 보상 지급 시점에만 개입
|
||||
4. 랭킹 등록은 **클라 정보를 서버가 그대로 저장**
|
||||
|
||||
---
|
||||
|
||||
## 1. 서버 스택 현황
|
||||
|
||||
| 구성 요소 | 현 상태 | 비고 |
|
||||
|----------|--------|------|
|
||||
| PlayFab 인증·UserData·TitleData | 사용 중 | 유지 |
|
||||
| CloudScript | 사용 중 (`Get_UserInfo`·`Get_MailList`·`Get_MailReward` 등) | 함수 dump 후 git 편입 필요 |
|
||||
| Leaderboard (Progression API) | 읽기 경로 존재 (`GetLeaderboard`·`GetLeaderboardAroundEntity`) | 등록 경로 미확인 → 재설계 필요 |
|
||||
| Mail | `Get_AllMail` CloudScript 존재 | 재화 보상 우편 처리 |
|
||||
| `Save_StageResult` (스테이지 완료 처리) | **비활성 상태** (주석 처리) | 복원·신규 설계 필요 |
|
||||
| `InappInfo.cs` (IAP 영수증) | **비활성 상태** | 재활성화 + `ValidateGooglePlayPurchase` 연결 |
|
||||
| `Server_Live_ID`·`Server_Dev_ID` | 코드 주석 처리 상태 | 인수인계 시 복구 |
|
||||
|
||||
---
|
||||
|
||||
## 2. 서버 관리 데이터 카테고리
|
||||
|
||||
| # | 도메인 | 주요 필드 | SOT 방향 |
|
||||
|---|--------|----------|---------|
|
||||
| 1 | 재화 인벤토리 | `dic_Item[itemid] = amount` (Gold·Soul 등) | 서버 SOT 필수 |
|
||||
| 2 | PC(캐릭터) 보유 | `dic_PC[pcid]` — 레벨·경험치·각성 | 서버 SOT |
|
||||
| 3 | 장비·카드 | `Equipment`·`dic_Card[cardid]` | 서버 SOT |
|
||||
| 4 | 스테이지 진행 | `StageData.dic_stagedata[diff][chapter][stageid].Star` | 서버 SOT (보상 지급 연결) |
|
||||
| 5 | 미션·출석 | `Mission.dic_Mission[type]`·`RewardAttandanceDay` | 서버 SOT (재화 보상) |
|
||||
| 6 | 랭킹 | PlayFab Leaderboard | 서버 저장 |
|
||||
| 7 | 우편 | CloudScript 기반 | 서버 SOT |
|
||||
| 8 | 시즌·패스 | 신규 정의 필요 | 기획팀 SOT 정의 후 구체화 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 클라/서버 경계 — 핵심 원칙 3
|
||||
|
||||
1. **재화 사용·획득 → 서버 응답 필수**
|
||||
- 클라: 사용·획득 요청 전송 / 서버: 잔고 검증·차감·응답 (또는 지급·응답)
|
||||
- 서버 거부 시 클라 롤백
|
||||
|
||||
2. **미션 클리어 → 클라 판단, 재화 보상은 서버 지급**
|
||||
- 클라: 조건 체크·카운트 누적·달성 판정 / 서버: 보상 수령 요청 수신 시 지급
|
||||
|
||||
3. **랭킹 등록 → 클라가 계산한 값을 서버가 그대로 저장**
|
||||
- 클라: 점수·메타정보 계산·전송 / 서버: Leaderboard 저장
|
||||
|
||||
**보상 통일**: 모든 보상은 재화 단위. 비재화 보상(칭호·스킨·장비·PC 등) 없음.
|
||||
|
||||
---
|
||||
|
||||
## 4. 핵심 API 3종
|
||||
|
||||
### 4-1. `Save_StageResult` (복원 필수 — 샘플)
|
||||
|
||||
**유형**: CloudScript 함수 / **호출 권한**: Client (LoginSession 유효)
|
||||
**역할**: 스테이지 클리어 결과를 저장하고 재화를 지급
|
||||
|
||||
**요청 파라미터** (예시):
|
||||
```json
|
||||
{
|
||||
"difficulty": 2,
|
||||
"chapter": 3,
|
||||
"stageId": 47,
|
||||
"starCount": 3,
|
||||
"clearTimeMs": 125000,
|
||||
"droppedItems": [{"itemId": 101, "amount": 500}],
|
||||
"is_abuse_flag": false
|
||||
}
|
||||
```
|
||||
|
||||
**응답 (성공)**:
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"grantedRewards": [{"itemId": 101, "amount": 500}],
|
||||
"newStar": 3,
|
||||
"stageProgress": {"maxStageId": 47}
|
||||
}
|
||||
```
|
||||
|
||||
**응답 (실패)**:
|
||||
| 에러 코드 | 조건 | 클라 처리 |
|
||||
|----------|------|----------|
|
||||
| `AbuseFlagged` | `is_abuse_flag: true` 수신 | 지급 거부, 기록 저장 (섹션 5) |
|
||||
| `SessionInvalid` | `Save_IngameStart` 세션 없음·만료 | 재로그인 유도 |
|
||||
| `StageLocked` | 해당 스테이지 미해금 | 경로 이탈 처리 |
|
||||
|
||||
### 4-2. `Grant_MissionReward`
|
||||
- 미션 보상 재화 지급. 클라가 미션 완료 판정 후 수령 요청 시 서버가 지급·응답.
|
||||
- `is_abuse_flag` 동일 수용 (클라가 자체 판정 시 true 동봉 가능).
|
||||
|
||||
### 4-3. 랭킹 등록 경로 (신규 설계)
|
||||
- 현 코드에서 등록 호출처 미확인 → 인간 개발자가 재구축. 엔드포인트명 예: `Submit_RankingEntry`.
|
||||
- 클라 전송 점수·메타정보 그대로 Leaderboard 저장. 세부는 D-3 설계 문서에서 확정.
|
||||
|
||||
---
|
||||
|
||||
## 5. 참고: 어뷰징 방지 체계 (클라 주도, 서버 최소 역할)
|
||||
|
||||
> **2026-04-17 PD님 직접 재확정**: 어뷰징 **판정은 클라이언트 100% 책임**. 서버는 판정 결과(플래그)만 받아 처리.
|
||||
|
||||
**판정 책임**: 클라이언트 (경계값 보관·수치 검증 전부 클라에서 수행)
|
||||
|
||||
**서버 역할 (최소)**:
|
||||
- 재화 지급 요청 페이로드에 **`is_abuse_flag: true`** 가 포함된 경우, 해당 지급을 **거부**하고 **기록** 저장
|
||||
- 플래그 저장 위치: PlayFab Profile 필드 또는 별도 모니터링 엔드포인트 (배정 시 결정)
|
||||
- 관리자 알림 채널(Slack Webhook·이메일 등) 연계는 배정 후 결정
|
||||
|
||||
**서버가 하지 않는 것**:
|
||||
- 경계값 테이블 보관 (Title Data 적재 불필요)
|
||||
- 경계값과 클라 전송 수치 비교 검증
|
||||
- 어뷰징 판정 로직 자체
|
||||
|
||||
**세부 설계**: 기획팀 `공유/소통/기획팀→PM/2026-04-17_어뷰징판정_솔루션_기획서_v1.md` 참조 (클라이언트팀 구현 시 필요 시 서버 개발자와 **플래그 인터페이스만** 협의).
|
||||
|
||||
---
|
||||
|
||||
## 6. 환경 셋업 체크리스트
|
||||
|
||||
- [ ] PlayFab 계정 접근 권한 수령 (Title ID 인수·Game Manager 대시보드·CloudScript revision 접근)
|
||||
- [ ] 현 배포 CloudScript 전문 dump → git 편입 (경로는 PM 협의, 후보: `코어코드/수상한잡화점_서버/`)
|
||||
- [ ] Unity 프로젝트 clone + `git fetch origin && git pull` (C30 준수)
|
||||
- [ ] Unity 에디터 설치 (프로젝트 버전 기준)
|
||||
- [ ] PlayFab SDK 연동 확인 (빌드·실행 1회 성공)
|
||||
- [ ] IDE (VS Code 또는 JetBrains Rider) + Node.js (CloudScript 로컬 테스트용)
|
||||
|
||||
---
|
||||
|
||||
## 7. 결정 대기 2건 (PD·PM 후속)
|
||||
|
||||
1. **PD-③ 서버 스택 유지 여부** — PlayFab 유지(A안, 본 지시서 전제) vs 하이브리드(B안) vs 자체 서버(C안). 인간 개발자 배정 후 기술 선호·경험 수렴하여 PM 경유 PD님 보고.
|
||||
2. **PD-④ 세이브 SOT 전환 범위** — 현 "로컬 1차 + 클라우드 보조" 하이브리드 유지(A안) vs 서버 1차 SOT(B안). 개발팀 의견: 수상한잡화점 현 시점 A안, 차기 프로젝트 B안 (코어 프레임워크 반영).
|
||||
|
||||
**PD-⑤ 일일/주간 리셋 시간 기준** — 서버 시간 기준 전환 확정 (개발팀 재량, [필수 작업]).
|
||||
|
||||
---
|
||||
|
||||
## 8. 용어집
|
||||
|
||||
| 용어 | 뜻 |
|
||||
|------|---|
|
||||
| PlayFab | Microsoft BaaS. 본 프로젝트 서버 스택 기반 |
|
||||
| CloudScript | PlayFab 서버 사이드 JavaScript 실행 환경 |
|
||||
| Title Data | PlayFab Title 단위 공용 설정 저장소 |
|
||||
| UserData | PlayFab 유저별 서버 저장소 |
|
||||
| Leaderboard | PlayFab 순위표 서비스 (Progression API) |
|
||||
| ObscuredType | `CodeStage.AntiCheat` 로컬 변조 방어 타입. 서버 검증 대체재 아님 |
|
||||
| ServerData | 수상한잡화점 프로젝트 클래스(`ServerClass.cs`). 유저 데이터 SOT 로컬 표현 |
|
||||
| SOT | Source of Truth. 데이터의 단일 진실 근원 |
|
||||
| AntiCheat | 어뷰징·치트 방지 체계. 본 프로젝트는 **클라 판정** + **서버는 플래그 수신 시 지급 거부** |
|
||||
|
||||
---
|
||||
|
||||
## 9. 기각안 (P24·P27 결정·설계 엔트리 필수)
|
||||
|
||||
1. **"어뷰징 경계값 서버 보관·검증" 안 (v1.0 B-7 구조)** — PD님 재결정으로 기각. 서버가 경계값 테이블을 Title Data에 적재하고 수치 검증하는 구조는 폐기. 어뷰징은 **클라 주도 작업**이며 서버 개발자 작업 스펙 아님.
|
||||
2. **"상세 설계 전부 포함한 장문 지시서" 안 (v1.0 446줄)** — 인간 개발자 파악 시간 낭비. 요약판 원칙으로 전환 (PD님 지시).
|
||||
3. **"비재화 보상 유지" 안** — PD 확정(모든 보상=재화)으로 전제 소멸. 기각.
|
||||
4. **"PlayFab 전면 폐기 + 자체 서버 전면 신규" 안** — 현 보류 항목 해소 전 범위 확대 리스크. PD-③ 옵션으로만 제시.
|
||||
5. **"배정 전 세부 API 스펙 전량 확정" 안** — 배정된 개발자의 기술 선호 반영 없는 스펙은 재작업 리스크. 본 지시서는 원칙·경계·샘플 1건까지, 세부는 배정 후 설계 문서에서 확정.
|
||||
|
||||
---
|
||||
|
||||
## 10. 변경 이력
|
||||
|
||||
| 일시 | 버전 | 작성자 | 요지 |
|
||||
|------|------|--------|------|
|
||||
| 2026-04-17 | v1.0 | 개발팀장 | 최종본 초판 (B-7 어뷰징 방지 서버 연계 포함). PD님 어뷰징 책임 재확정으로 대체됨 |
|
||||
| 2026-04-17 | **v1.1** | 개발팀장 | **요약판 재작성**: (1) 어뷰징 판정 클라 100% 책임 확정 반영 — 서버는 플래그 수신만 (섹션 5). (2) 446줄 → 150줄 이내 축약. (3) 샘플 API 1건 유지, 템플릿·매트릭스 세부는 D-3 설계 문서로 이관 |
|
||||
|
||||
---
|
||||
|
||||
**서명**: 개발팀장 (인간 서버 개발자 배정 시 본 지시서를 출발점으로 사용)
|
||||
**DOCX 변환**: PM이 `anthropic-skills:docx`로 재생성
|
||||
**후속 트래킹**: 개발팀 PD 지시 로그 #30 v1.1 갱신 + #신규 "어뷰징 책임 재확정·요약판 재작성" 등록·완료
|
||||
|
|
@ -0,0 +1,378 @@
|
|||
---
|
||||
type: 기획서
|
||||
project: 수상한잡화점
|
||||
version: v1
|
||||
status: 진행중
|
||||
author: 기획팀장
|
||||
date: 2026-04-17
|
||||
scope: 어뷰징 판정 프레임워크 (설계 원칙 + 스키마 + 예시 틀)
|
||||
related_rules: [C7, C11, C13, C23, P23]
|
||||
related_docs:
|
||||
- 공유/소통/기획팀→PM/2026-04-17_Unity_MCP_시뮬레이션_기획검토_기획팀.md
|
||||
- 프로젝트/수상한잡화점/기획/Phase3_재개준비_체크리스트_v1.md
|
||||
---
|
||||
|
||||
# 어뷰징 판정 솔루션 기획서 v1
|
||||
|
||||
## 요지
|
||||
|
||||
스테이지 클리어 보상·랭킹 등록·일일 미션에서 클라가 전송하는 데이터를 서버가 **"시뮬레이터 산정 이론 상한 경계값"** 기반으로 검증하여, 경계를 초과하는 요청을 어뷰저로 판정·차단한다. 기획팀은 경계값 도출 방법론과 테이블 스키마를 제공하고, 서버는 검증 로직을 탑재한다. 실제 수치는 Unity MCP 시뮬 가동 결과로 채워진다(본 기획서는 틀만 제공).
|
||||
|
||||
**재미 관점(C7)**: 어뷰징이 방치되면 랭킹·미션의 도전 가치가 붕괴되고, 정상 플레이어의 성취감이 훼손된다. 본 솔루션의 목표는 "정직한 플레이어의 재미를 보호"하는 것이지 선의의 실력자를 잡는 것이 아니다. 따라서 경계값은 **이론 상한에 안전 마진을 더한 값**으로 설정하여 false positive를 최소화한다.
|
||||
|
||||
---
|
||||
|
||||
## A. 문제 정의
|
||||
|
||||
### A-1. 전제
|
||||
- 모든 보상(미션·스테이지·랭킹) = 재화 형태로 지급 확정 (2026-04-17 PD님 결정)
|
||||
- 스테이지 클리어·랭킹 등록에서 서버는 클라 전송 데이터를 기반으로 보상을 산정/기록한다
|
||||
- 클라를 무조건 신뢰하면 메모리 조작·패킷 위변조를 통한 재화 부당 획득이 가능하다
|
||||
|
||||
### A-2. 어뷰징 공격 벡터
|
||||
|
||||
| 벡터 ID | 대상 액션 | 조작 포인트 | 파급 위험 |
|
||||
|--------|----------|-----------|---------|
|
||||
| **AV-1** | 스테이지 클리어 완료 패킷 | 클리어타임 단축 (예: 0초·음수) | 시간 가속 보상 파밍 |
|
||||
| **AV-2** | 스테이지 클리어 완료 패킷 | 획득 재화량 조작 (상한 초과) | 재화 무한 생성 |
|
||||
| **AV-3** | 스테이지 클리어 완료 패킷 | 별(★) 개수 조작 (3/3 고정) | 미달성 보상 부당 수령 |
|
||||
| **AV-4** | 스테이지 클리어 완료 패킷 | 소모 자원 0 보고 (물약·쉴드·HP) | ★조건 달성 위조 (C6 포션절제, N2 피격제한 등) |
|
||||
| **AV-5** | 랭킹 등록 패킷 | 점수값 조작 (이론 상한 초과) | 랭킹 1위 탈취 |
|
||||
| **AV-6** | 랭킹 등록 패킷 | 연관 메타값 조작 (사용 카드·편성 등 검증 소재) | 검증 회피 |
|
||||
| **AV-7** | 일일 미션 카운트 | 카운트 중복 전송·상한 초과 | 일일 한도 초과 보상 수령 |
|
||||
| **AV-8** | 리플레이/재접속 재전송 | 동일 클리어 결과 재제출 | 중복 보상 |
|
||||
| **AV-9** | 스테이지 잠금 우회 | 언락 안 된 스테이지 완료 패킷 | 진행도 건너뛰기 |
|
||||
|
||||
### A-3. 판정 대상 범주 3종
|
||||
|
||||
1. **물리적 불가능** — 시뮬레이터로도 달성 불가능한 수치 (예: 클리어타임 0초, 점수 int.MaxValue)
|
||||
2. **확률적 극단** — 이론상 가능하나 확률이 충분히 낮아 사실상 어뷰징으로 간주 (예: 운으로 1회 나올 수치의 연속 달성)
|
||||
3. **논리적 모순** — 내부 값들이 서로 모순 (예: "피격 0회"인데 "HP 50% 소진")
|
||||
|
||||
---
|
||||
|
||||
## B. 경계값 도출 방법론
|
||||
|
||||
### B-1. 기본 공식
|
||||
|
||||
```
|
||||
경계값 = 시뮬_이론_극값 × 안전마진계수
|
||||
```
|
||||
|
||||
- `시뮬_이론_극값`: Unity MCP 시뮬을 N회(권장 N=1,000 이상) 수행하여 얻은 **최상위 극값** (최대/최소는 벡터에 따라 다름)
|
||||
- `안전마진계수`: 벡터 특성에 따라 결정하는 완화 계수 (정상 플레이어 오차 허용)
|
||||
|
||||
### B-2. 안전마진 설계 원칙
|
||||
|
||||
| 벡터 특성 | 마진 방향 | 계수 예시 | 근거 |
|
||||
|---------|--------|---------|------|
|
||||
| 클리어타임 (짧을수록 의심) | 하한 경계값 = 이론 최단 × **0.80** | 0.80 | 프레임 드랍·클라 시계 오차로 정상 유저도 5~15% 짧게 보고 가능 |
|
||||
| 획득 재화 (많을수록 의심) | 상한 경계값 = 이론 최대 × **1.05** | 1.05 | 소숫점 반올림·버프 중첩 경계 케이스 흡수 |
|
||||
| 랭킹 점수 (높을수록 의심) | 상한 경계값 = 이론 최대 × **1.10** | 1.10 | 신규 카드 출시 등 업데이트 이후 시뮬 갱신 전 과도기 흡수 |
|
||||
| 피격 수 (적을수록 의심) | 하한 경계값 = 이론 최소 (= 0) | - | 0회도 이론상 가능, 음수만 차단 |
|
||||
| 미션 카운트 | 상한 경계값 = 일일 최대 달성 가능치 × **1.00** | 1.00 | 마진 불필요 (정수 카운트) |
|
||||
|
||||
**주의**: 마진은 "정상 유저를 어뷰저로 오판정하지 않기 위한" 안전장치지, "어뷰저를 놓치는 관용"이 아니다. 극값의 80%~110% 범위는 경계이므로 이 범위 유저는 "**1차 통과 + 플래그 미부여**"로 처리한다.
|
||||
|
||||
### B-3. 업데이트 주기
|
||||
|
||||
1. **정기 갱신**: 밸런스 패치(카드 수치 변경·신규 몬스터 추가) 시 해당 영역 시뮬 재실행
|
||||
2. **긴급 갱신**: 어뷰징 신고 접수 또는 랭킹 이상치 감지 시 즉시 시뮬 재실행·경계값 조정
|
||||
3. **버전 관리**: 경계값 테이블은 C6에 따라 `.bak_{YYYYMMDD_HHMM}.json` 백업 후 교체
|
||||
4. **C13 기록**: 경계값 변경은 PD 지시 로그·대화로그에 기각안 포함 기록 (P24)
|
||||
|
||||
### B-4. 시뮬레이터 입력 조건 (Unity MCP)
|
||||
|
||||
각 스테이지·각 벡터에 대해 다음 조건의 최극단 조합을 시뮬:
|
||||
- 캐릭터: 최강 편성 (풀레벨·최적 카드)
|
||||
- 적 패턴: 최선 운 (치명타 확률 최대·적 회피 최소)
|
||||
- 플레이 입력: 완벽 입력 (반응시간 0·공백 없음)
|
||||
- 물약·쉴드: 최대 활용 (획득 재화 벡터) / 미사용 (C6 조건 벡터)
|
||||
|
||||
**출력 수집**: 10,000회 반복하여 상위/하위 0.1% 값을 "이론 극값"으로 채택 (절대 최대/최소 아닌 이유 = 시뮬 자체 오차 흡수).
|
||||
|
||||
---
|
||||
|
||||
## C. 검증 계층 설계
|
||||
|
||||
### C-1. 2계층 검증 원칙
|
||||
|
||||
**1계층 (클라)** — 예방적 차단
|
||||
- 목적: 악의적 조작이 아닌 **버그·의도치 않은 극값**을 클라 단에서 거르기
|
||||
- 클라가 자체 생성한 결과값이 경계값을 초과하면 전송 보류 + 경고 로그 수집
|
||||
- **클라 신뢰는 하지 않음** — 이 계층만으로는 판정 금지 (메모리 조작 시 이 계층 무력화 가능)
|
||||
|
||||
**2계층 (서버)** — 최종 판정
|
||||
- 목적: 모든 어뷰징 판정의 **단일 결정권자**
|
||||
- 서버가 경계값 테이블을 보유하고 수신 패킷 전량 검증
|
||||
- 경계 초과 시: 거부 + 플래그 기록 + (심각도에 따라) 관리자 알림
|
||||
|
||||
### C-2. 검증 흐름 (서버)
|
||||
|
||||
```
|
||||
[클라 패킷 수신]
|
||||
↓
|
||||
[Step 1] 기본 유효성 (음수·null·형식 오류) → 거부
|
||||
↓
|
||||
[Step 2] 스테이지 잠금 상태 확인 (AV-9 대응) → 거부
|
||||
↓
|
||||
[Step 3] 중복 제출 확인 (AV-8 대응, 같은 세션ID·클리어ID) → 거부
|
||||
↓
|
||||
[Step 4] 경계값 테이블 조회 → 각 필드 범위 검증
|
||||
↓ (통과)
|
||||
[Step 5] 논리 정합성 검증 (필드 간 모순, A-3-3 범주)
|
||||
↓ (통과)
|
||||
[정상 처리 + 보상 지급]
|
||||
|
||||
↓ (Step 4·5 실패 시)
|
||||
[플래그 기록 + 보상 보류 + 심각도별 조치]
|
||||
```
|
||||
|
||||
### C-3. 역할 분담 원칙
|
||||
|
||||
| 주체 | 역할 | 금지 |
|
||||
|-----|-----|-----|
|
||||
| 클라 | 버그 조기 감지·UX 경고 | 최종 판정, 플래그 기록 |
|
||||
| 서버 | 최종 판정, 플래그 DB 기록, 관리자 알림 | 클라 통과 가정·검증 생략 |
|
||||
| 기획팀 | 경계값 테이블 공급 | 서버 검증 로직 직접 작성 |
|
||||
| 인간 서버 개발자 | 검증 로직 구현·플래그 DB 스키마 | 경계값 자체 수정 (기획 승인 없이) |
|
||||
|
||||
---
|
||||
|
||||
## D. 어뷰저 플래그 체계
|
||||
|
||||
### D-1. 플래그 종류 3단계
|
||||
|
||||
| 단계 | 트리거 | 자동 조치 | 관리자 조치 |
|
||||
|-----|-------|---------|-----------|
|
||||
| **F1 경미** | 경계값의 +5%~+10% 범위 이내 초과 (마진 범위 경계) | 플래그 기록만 | 누적 3회 이상 시 F2 격상 |
|
||||
| **F2 중대** | 경계값의 +10%~+50% 초과, 또는 F1 3회 누적 | 해당 보상 보류 + 관리자 알림 | 수동 검토 후 재화 회수·경고 |
|
||||
| **F3 확정** | 경계값의 +50% 초과, 논리 모순 발생, 또는 F2 2회 누적 | 해당 보상 미지급 + 계정 플래그 | 계정 정지 검토 (랭킹 삭제 포함) |
|
||||
|
||||
### D-2. 플래그 누적 처리
|
||||
|
||||
- 플래그 DB: `{user_id, timestamp, vector_id, flag_level, raw_data, boundary_value, reason}`
|
||||
- 기간 윈도우: 최근 30일 기준 누적 (기획팀 권고안, 서버측 조정 가능)
|
||||
- 에스컬레이션: F1×3 → F2 / F2×2 → F3 / F3×1 → 즉시 계정 정지 검토 대상
|
||||
|
||||
### D-3. 기획팀 권고안 (PM 경유 운영팀 협의 필요)
|
||||
|
||||
- **F1**: 자동 회수 없음 (false positive 위험). 사용자에게 경고도 노출하지 않음 (어뷰저 학습 방지)
|
||||
- **F2**: 해당 회차 보상만 회수. 사용자에게는 "서버 동기화 오류, 재시도" 메시지 노출
|
||||
- **F3**: 계정 정지는 관리자 최종 판단. 랭킹 등록 데이터는 즉시 삭제
|
||||
|
||||
---
|
||||
|
||||
## E. 대상 액션별 구체 경계값 초안
|
||||
|
||||
> **주의**: 아래 수치는 모두 **플레이스홀더**. Unity MCP 시뮬 가동 후 확정. 실제 경계값 테이블은 별도 JSON으로 관리.
|
||||
|
||||
### E-1. 스테이지 클리어
|
||||
|
||||
| 필드 | 검증 규칙 | 경계값 예시 (Stage 1) |
|
||||
|-----|---------|----------------------|
|
||||
| `clearTime` | `sim_min × 0.80` 하한, `sim_max × 1.20` 상한 | 하한: 미확정(시뮬 후) / 상한: 미확정 |
|
||||
| `goldEarned` | `sim_max × 1.05` 상한 | 상한: 미확정(시뮬 후) |
|
||||
| `starsEarned` | 0~3 정수, ★조건 메타값과 교차 검증 | 0~3 |
|
||||
| `hpRemaining` | `0 ≤ hp ≤ maxHp` | 캐릭터 maxHp 기반 |
|
||||
| `potionUsed` | 0 이상 정수, 소지 한도 이하 | 캐릭터 포션 한도 |
|
||||
| `shieldRemaining` | `0 ≤ shield ≤ maxShield` | 캐릭터 maxShield 기반 |
|
||||
| `hitsTaken` | 0 이상 정수, `sim_max × 1.20` 상한 | 상한: 미확정(시뮬 후) |
|
||||
|
||||
### E-2. 랭킹 등록
|
||||
|
||||
| 필드 | 검증 규칙 | 경계값 예시 |
|
||||
|-----|---------|----------|
|
||||
| `score` | `sim_max × 1.10` 상한 | 상한: 미확정(시뮬 후) |
|
||||
| `clearTime` | 동일 스테이지 E-1 규칙 적용 | - |
|
||||
| `deckComposition` | 소유 카드 목록과 교차 검증 | - |
|
||||
| `stageId` | 유효 스테이지 ID (잠금 해제 상태) | - |
|
||||
|
||||
### E-3. 일일 미션
|
||||
|
||||
| 필드 | 검증 규칙 | 경계값 예시 |
|
||||
|-----|---------|----------|
|
||||
| `missionId` | 유효 미션 ID + 당일 활성 | - |
|
||||
| `currentCount` | `missionMaxCount` 상한 | 미션별 상한 |
|
||||
| `incrementDelta` | 단일 트리거로 1 증가만 허용 | 1 |
|
||||
| `completionTimestamp` | 일일 리셋 시각 이후 ~ 현재 | - |
|
||||
|
||||
### E-4. ★조건 교차 검증 (P17 연계)
|
||||
|
||||
각 스테이지의 활성 ★조건(C1~C9, N1~N6)과 클리어 결과 필드가 정합해야 한다. 예:
|
||||
- **C6 (포션 절제)**: `potionUsed == 0` 인지 확인
|
||||
- **N2 (피격 제한)**: `hitsTaken ≤ 서브맵수 × 1` 인지 확인
|
||||
- **N4 (쉴드 하한 30%)**: `shieldRemaining ≥ maxShield × 0.30` 인지 확인
|
||||
|
||||
★조건 통과 보고와 필드 값이 모순되면 **F3 확정 플래그** (논리 모순).
|
||||
|
||||
---
|
||||
|
||||
## F. 서버 측 요구사항 (개발팀 인계용)
|
||||
|
||||
### F-1. 기획팀 공급 산출물
|
||||
|
||||
1. **경계값 테이블** — JSON 파일 (Unity MCP 시뮬 결과 반영)
|
||||
2. **★조건 검증 규칙** — 스테이지별 활성 조건 + 판정 로직
|
||||
3. **플래그 기준** — F1/F2/F3 경계 정의
|
||||
4. **갱신 공지** — 경계값 변경 시 개발팀에 PR 또는 소통 채널 통지
|
||||
|
||||
### F-2. 경계값 테이블 스키마 (JSON 예시)
|
||||
|
||||
```json
|
||||
{
|
||||
"version": "1.0.0",
|
||||
"generatedAt": "2026-04-17T00:00:00Z",
|
||||
"simulationRuns": 10000,
|
||||
"stages": {
|
||||
"stage_01": {
|
||||
"clearTime": {
|
||||
"min": 42.3,
|
||||
"max": 180.0,
|
||||
"marginLow": 0.80,
|
||||
"marginHigh": 1.20,
|
||||
"boundaryMin": 33.84,
|
||||
"boundaryMax": 216.0
|
||||
},
|
||||
"goldEarned": {
|
||||
"max": 250,
|
||||
"marginHigh": 1.05,
|
||||
"boundaryMax": 263
|
||||
},
|
||||
"hitsTaken": {
|
||||
"min": 0,
|
||||
"max": 8,
|
||||
"marginHigh": 1.20,
|
||||
"boundaryMax": 10
|
||||
},
|
||||
"starConditions": {
|
||||
"C2": { "field": "hitsTaken", "operator": "==", "value": 0 },
|
||||
"C6": { "field": "potionUsed", "operator": "==", "value": 0 },
|
||||
"N2": { "field": "hitsTaken", "operator": "<=", "formula": "subMapCount * 1" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"ranking": {
|
||||
"stage_01": {
|
||||
"score": { "max": 12500, "marginHigh": 1.10, "boundaryMax": 13750 }
|
||||
}
|
||||
},
|
||||
"dailyMissions": {
|
||||
"login": { "maxCount": 1 },
|
||||
"clear_any_stage": { "maxCount": 10 }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### F-3. 검증 API 호출 흐름 (서버 내부)
|
||||
|
||||
```
|
||||
수신: POST /api/stage/complete
|
||||
body: { userId, stageId, clearTime, goldEarned, starsEarned, hitsTaken, potionUsed, ... }
|
||||
|
||||
서버 처리:
|
||||
1. AbuseValidator.validate(userId, stageId, body)
|
||||
→ boundaries = BoundaryTable.get(stageId)
|
||||
→ for each field: 경계값 비교
|
||||
→ starCondition 교차 검증
|
||||
→ 중복 제출 체크
|
||||
2. ValidationResult:
|
||||
- PASS: 보상 지급 처리
|
||||
- F1: 보상 지급 + 플래그 기록
|
||||
- F2: 보상 보류 + 플래그 기록 + 관리자 알림
|
||||
- F3: 보상 미지급 + 플래그 기록 + 계정 플래그
|
||||
3. 응답:
|
||||
- PASS/F1: { status: "success", rewards: [...] }
|
||||
- F2: { status: "pending", message: "서버 동기화 오류, 재시도 바랍니다" }
|
||||
- F3: { status: "blocked" } (어뷰저 학습 방지 위해 구체 사유 미노출)
|
||||
```
|
||||
|
||||
### F-4. 위반 응답 포맷 (권고)
|
||||
|
||||
어뷰저 학습 방지를 위해 구체 수치·사유를 클라에 노출하지 않는다:
|
||||
|
||||
```json
|
||||
// F1 (경미)
|
||||
{ "status": "success", "rewards": [...] } // 플래그는 서버 내부만, 클라 무감지
|
||||
|
||||
// F2 (중대)
|
||||
{ "status": "pending", "message": "서버 동기화 오류, 잠시 후 재시도" }
|
||||
|
||||
// F3 (확정)
|
||||
{ "status": "blocked" }
|
||||
```
|
||||
|
||||
### F-5. 플래그 DB 스키마 (서버 구현 시 참조)
|
||||
|
||||
```
|
||||
AbuseFlag:
|
||||
- id: UUID
|
||||
- userId: string
|
||||
- timestamp: datetime
|
||||
- vectorId: enum (AV-1 ~ AV-9)
|
||||
- flagLevel: enum (F1, F2, F3)
|
||||
- stageId: string (nullable)
|
||||
- fieldName: string
|
||||
- rawValue: json
|
||||
- boundaryValue: json
|
||||
- exceedRate: float // (rawValue - boundary) / boundary
|
||||
- reason: string
|
||||
- reviewedBy: string (nullable)
|
||||
- reviewedAt: datetime (nullable)
|
||||
- resolution: enum (pending, confirmed_abuse, false_positive, pardoned)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## G. 기각안
|
||||
|
||||
### G-1. 서버 단독 시뮬 재현 검증 (채택 안 함)
|
||||
|
||||
- **안**: 서버가 클라 입력(키·타이밍)을 받아 서버에서 전투를 재시뮬하여 결과 일치 여부 검증
|
||||
- **기각 사유**: (1) 서버 CPU 비용 폭증 — 1회 재시뮬 ≈ 1개 전투 전체 연산 (2) 동기화 이슈 (난수 시드·부동소수점) (3) 현 프로젝트가 실시간 멀티플레이가 아닌 싱글 플레이 구조라 과잉 투자. **채택 안**인 "경계값 기반 통계적 검증"이 비용·구현 난이도·효과 3축에서 우수.
|
||||
|
||||
### G-2. 머신러닝 기반 이상치 탐지 (채택 안 함)
|
||||
|
||||
- **안**: 유저 플레이 데이터를 수집하여 ML 모델로 어뷰저 이상치 자동 탐지
|
||||
- **기각 사유**: (1) 학습 데이터 축적에 운영 기간 필요 — 초기 출시 대응 불가 (2) false positive 조정 난해 (3) 인간 서버 개발자 부담 가중. 장기적 보조 수단으로는 고려 가치 있으나 v1 범위에서 제외. 본 기획서는 시뮬 경계값으로 출시 대응 + 운영 후 데이터 축적 → 차후 ML 도입 여지 유지.
|
||||
|
||||
### G-3. 클라 단독 판정 (채택 안 함)
|
||||
|
||||
- **안**: 클라가 자체 검증 후 서버에는 결과만 전송
|
||||
- **기각 사유**: 메모리 조작·패킷 위변조로 무력화됨. 어뷰징 방지 근본 목적 미달성. **원천적으로 고려 대상 아님**.
|
||||
|
||||
### G-4. 전체 플레이 로그 전송·서버 기록 (채택 안 함)
|
||||
|
||||
- **안**: 매 프레임 입력 로그 전체를 서버로 전송하여 서버가 사후 감사
|
||||
- **기각 사유**: (1) 네트워크 비용 과도 (2) 저장소 비용 (3) G-1과 유사하게 오버엔지니어링. F3 확정 플래그 시점에 사후 감사용으로 마지막 30초~60초 정도만 보관하는 방안은 서버측 재량.
|
||||
|
||||
### G-5. 안전 마진 0% 엄격 검증 (채택 안 함)
|
||||
|
||||
- **안**: 시뮬 극값을 그대로 경계값으로 사용하여 극한 배제
|
||||
- **기각 사유**: false positive 폭발. 프레임 드랍·반올림 오차로 정상 유저도 매번 플래그됨. **C7 재미 우선 원칙 위반** — 정상 플레이어 경험 훼손. 5%~20% 마진 보정 필수.
|
||||
|
||||
---
|
||||
|
||||
## 후속 작업 (Unity MCP 시뮬 가동 후 수행)
|
||||
|
||||
1. 각 스테이지·각 벡터별 시뮬 10,000회 실행
|
||||
2. 경계값 테이블 JSON 1차 산출 (v1.0.0)
|
||||
3. balance-designer 검토 → 재미 관점 마진 재조정
|
||||
4. 개발팀과 스키마·API 계약 확정 (`공유/소통/기획팀→개발팀/` 발행)
|
||||
5. 플래그 DB 스키마 서버팀 리뷰·구현
|
||||
6. 통합 QA 시 어뷰저 시뮬 툴로 F1/F2/F3 각각 검증 케이스 생성
|
||||
|
||||
## 연관 규칙·문서
|
||||
|
||||
- **C7**: 재미 우선 — 정상 플레이어 보호를 위한 마진 설계 근거
|
||||
- **C11**: 개발 관점 — 서버 자원 효율 고려하여 재시뮬 대신 경계값 비교 채택 (G-1 기각 근거)
|
||||
- **C13**: PD 지시 트래킹 — 본 기획 등록 및 경계값 변경 이력 관리
|
||||
- **C23**: 허위 보고 금지 — 본 기획서는 실제 시뮬 가동 전이므로 수치는 모두 플레이스홀더 명시
|
||||
- **P17**: ★조건 배타 배치 — E-4 ★조건 교차 검증과 연계
|
||||
- **P23**: 기획 결정 재량 — 본 기획은 기획팀장 재량 범위(신규 시스템 제안은 PM 확인 필요 → 본 기획서 발행으로 PM 확인 경유)
|
||||
|
||||
---
|
||||
|
||||
**기획팀장 발의**
|
||||
**PM 검토 요청**: 본 기획서 스키마·프레임워크 승인 → 개발팀 F 섹션 인계 트리거
|
||||
**승인 후 착수**: Unity MCP 시뮬 가동 (별도 PD 지시 로그)
|
||||
|
|
@ -0,0 +1,142 @@
|
|||
---
|
||||
요청번호: REQ-XXX (일련번호 부여)
|
||||
요청일: YYYY-MM-DD
|
||||
요청부서: 기획팀
|
||||
수신부서: 개발팀
|
||||
담당에이전트: (예: /게임플레이, /클라이언트)
|
||||
우선순위: HIGH | MID | LOW
|
||||
상태: 대기 | 진행중 | 응답완료 | 보류
|
||||
유형: 밸런스수치_요구서
|
||||
관련PD지시: #N (해당 시)
|
||||
---
|
||||
|
||||
# 밸런스 수치 요구서 표준 템플릿
|
||||
|
||||
> **용도**: 기획팀이 개발팀에 밸런스 수치 변경·신규 테이블 반영을 요청할 때 사용하는 표준 포맷.
|
||||
> **원칙**: C7(재미 근거 필수) · C11(개발 관점 존중) · C6(백업 의무) · P16(변경 이력) · P24(기각안 기록).
|
||||
> **사용법**: 본 파일을 복사하여 `YYYY-MM-DD_REQ-XXX_요약제목.md`로 저장 후 채워 넣는다.
|
||||
|
||||
---
|
||||
|
||||
## 1. 요구서 식별
|
||||
|
||||
| 필드 | 값 |
|
||||
|------|-----|
|
||||
| **요구서 ID** | REQ-XXX |
|
||||
| **기준 버전** | (예: PCAwakening.json `v1.3.2`, 수상한잡화점_밸런싱전략_v1.md) |
|
||||
| **기준 커밋** | (git SHA 또는 "main@YYYY-MM-DD HH:MM") |
|
||||
| **작성자** | (기획팀장 / balance-designer 등) |
|
||||
|
||||
---
|
||||
|
||||
## 2. 변경 필드 목록
|
||||
|
||||
대상 파일·테이블과 변경 대상 필드를 명시.
|
||||
|
||||
| # | 파일·테이블 | 필드 경로 | 변경 유형 |
|
||||
|---|------------|---------|---------|
|
||||
| 1 | (예: `table_PCAwakening.json` > PC6001) | `s_Value` | 수정 |
|
||||
| 2 | (예: `카드시너지_v2.xlsm` > Sheet1!B5:B20) | `effect_coefficient` | 신규 |
|
||||
|
||||
**변경 유형**: `수정` / `신규` / `삭제` / `구조변경`
|
||||
|
||||
---
|
||||
|
||||
## 3. 변경 전후 수치
|
||||
|
||||
실제 수치를 전후 대비로 명시. 다건이면 표로, 단건이면 단락으로.
|
||||
|
||||
| # | 대상 | 현재값 | 제안값 | 비고 |
|
||||
|---|------|--------|--------|------|
|
||||
| 1 | PC6001 MaxHP Node 100394 `s_Value` | `'500%'` | `'50%'` | 데이터 입력 오류 정정 |
|
||||
| 2 | 스테이지 5 몬스터 HP | 1200 | 1400 | 난이도 곡선 조정 |
|
||||
|
||||
성장 곡선·구간 변경 시 구간별 전후 값 전수 명시.
|
||||
|
||||
---
|
||||
|
||||
## 4. 재미 근거 (C7 필수)
|
||||
|
||||
> **"어떤 재미를 강화하는가"를 먼저 정의**해야 수치 변경이 허용된다 (C7). 재미 정의 없는 수치 조정은 금지.
|
||||
|
||||
- **강화하려는 재미 축**: (예: "보스전 긴장감", "빌드 다양성", "만렙 성취감")
|
||||
- **변경 전 재미 문제점**: (예: "현재 만렙 DPS +1067%로 보스가 3초 내 클리어되어 긴장감 소실")
|
||||
- **변경 후 기대 경험**: (예: "만렙 DPS +50% 수준으로 보스 TTK 10~15초 구간 유지, 긴장감 보존")
|
||||
- **측정 지표**: (예: "Unity MCP 시뮬 100회 평균 TTK", "만렙 클리어율 70~85% 구간")
|
||||
|
||||
---
|
||||
|
||||
## 5. 개발 관점 우려 예상 (C11 존중)
|
||||
|
||||
> 기획팀이 개발팀 관점(C11 — 자원 효율성·코드 직관성·범용성)에서 예상되는 우려를 **사전 명시**하여 논의 효율을 높인다. 개발팀이 추가 우려를 발견 시 C3(은폐 금지)에 따라 즉시 제기.
|
||||
|
||||
| 관점 | 예상 우려 | 기획팀 입장 |
|
||||
|------|----------|-------------|
|
||||
| **자원 효율성** | (예: 매 프레임 재계산으로 CPU 부담) | (예: 변경 빈도 낮음 — 초기화 시 1회 계산 가능) |
|
||||
| **코드 직관성** | (예: `500%` vs `5.0` 혼재로 파싱 복잡) | (예: 신규 `s_Value_type` 컬럼 도입 제안) |
|
||||
| **범용성** | (예: 차기 프로젝트 재활용 어려움) | (예: 프로젝트 특수 로직으로 한정, 범용 모듈 분리) |
|
||||
|
||||
우려 없을 시 "없음 (기획팀 분석 범위 내 우려 없음)" 명시.
|
||||
|
||||
---
|
||||
|
||||
## 6. 검증 방법
|
||||
|
||||
변경이 의도대로 동작하는지 확인할 수 있는 검증 시나리오.
|
||||
|
||||
- **검증 채널**: Unity MCP 시뮬 / 로컬 빌드 / QA 수동 / 수치 단위 테스트
|
||||
- **검증 케이스**:
|
||||
1. (예: "만렙 PC 5종에 각성 트리 풀 해방 상태로 Stage 10 보스 진입 → 10회 반복 → TTK 평균 10~15초 확인")
|
||||
2. (예: "중간 단계 3레벨 상태 전투 시뮬 → DPS 증가율 +20~30% 구간 확인")
|
||||
- **통과 기준**: (검증 케이스별 수치 기준 명시)
|
||||
- **회귀 검증**: 변경 대상 외 기존 밸런스 경로(P14 QA 게이트) 영향 없음 확인
|
||||
|
||||
---
|
||||
|
||||
## 7. 백업·이력 (C6·P16)
|
||||
|
||||
- **백업 파일**: `{원본명}.bak_YYYYMMDD_HHMM.{확장자}` — 개발팀이 변경 착수 전 생성
|
||||
- **변경 이력 기록 위치**: 대상 md 문서 하단 "변경 이력 테이블"에 append
|
||||
- **관련 대화로그**: `공유/대화로그/수상한잡화점/YYYY-MM-DD.md` 엔트리 링크
|
||||
|
||||
---
|
||||
|
||||
## 8. 기각안 (P24 — 결정성 요청이면 권장)
|
||||
|
||||
검토했으나 채택하지 않은 대안과 기각 사유. 조직 자산 축적의 핵심 (헌법 제1원칙 목표 2 원칙 B).
|
||||
|
||||
| # | 기각 대안 | 기각 사유 |
|
||||
|---|----------|----------|
|
||||
| 1 | (예: 현재값 유지 + 별도 난이도 옵션 도입) | (예: 신규 시스템 비용 대비 재미 개선 불확실) |
|
||||
| 2 | (예: `500%` 일괄 → `100%`) | (예: 과도한 하향으로 각성 성취감 소실 우려) |
|
||||
|
||||
기각 대안 미검토 시 "없음 (단일안 — 사유: ...)" 명시. 공란 금지.
|
||||
|
||||
---
|
||||
|
||||
## 9. 응답 섹션 (개발팀 작성)
|
||||
|
||||
> 개발팀이 본 요구서에 응답할 때 아래 섹션을 append. 별도 응답 파일 분리 불필요 (본 파일 단일 SOT).
|
||||
|
||||
### 9-1. 개발 관점 검토 결과
|
||||
- 수용 가능 여부: 수용 / 조건부 수용 / 반려
|
||||
- 추가 우려: (기획팀 예상 외 발견 시)
|
||||
- 대안 제시: (반려·조건부 시)
|
||||
|
||||
### 9-2. 변경 적용 결과
|
||||
- 적용 커밋: (SHA)
|
||||
- 백업 경로: (`.bak_YYYYMMDD_HHMM.{확장자}`)
|
||||
- QA 검증 결과: (통과/실패 + 상세)
|
||||
- 회귀 검증: (통과/실패)
|
||||
|
||||
### 9-3. 후속 필요 작업
|
||||
- (예: 밸런스 조정 후 balance-designer 재튜닝 필요)
|
||||
- (예: Unity MCP 시뮬 재실행 필요 — 경계값 재산출)
|
||||
|
||||
---
|
||||
|
||||
## 변경 이력
|
||||
|
||||
| 일시 | 변경자 | 변경 내역 |
|
||||
|------|--------|----------|
|
||||
| 2026-04-17 | 기획팀장 | 표준 템플릿 신설 (PD님 직접 지시, 팀장 재량 진행 승인) |
|
||||
|
|
@ -0,0 +1,315 @@
|
|||
---
|
||||
from: 개발팀장
|
||||
to: PM
|
||||
date: 2026-04-17
|
||||
type: 보고서(초안)
|
||||
status: 진행중
|
||||
tags: [서버역할정리, 인간개발자지시서, 클라서버경계, PlayFab, 수상한잡화점]
|
||||
related: [C11, C29, C30, P13, P18, P23]
|
||||
depends_on: [PD-#2, PD-#5-B]
|
||||
---
|
||||
|
||||
# 인간 서버 개발자 업무 지시서 초안 — 서버 역할 정리
|
||||
|
||||
> **목적**: 인간 서버 개발자가 읽고 바로 작업 착수 가능한 **서버 역할 범위·데이터 카테고리·클라/서버 처리 경계 정의서** 초안. 최종 결정은 PD님 몫이며 본 문서는 **초안 + PD 결정 안건** 제시.
|
||||
>
|
||||
> **작성 방법 주석 (C23 정직성)**: 본 세션에서 `Task` 도구로 서버팀장·클라이언트팀장 서브에이전트 호출이 환경 제약으로 불가하여, 개발팀장이 **양쪽 관점을 명시적으로 구분해 단독 분석**. 섹션마다 `[서버 관점]`·`[클라 관점]` 태그로 구분. 서버팀장의 사전 업무공유체계 점검 보고(`공유/소통/개발팀→PM/2026-04-17_업무공유체계_점검_서버팀.md`)는 참조함.
|
||||
|
||||
---
|
||||
|
||||
## 0. 전제·근거 자료
|
||||
|
||||
### 0-1. PD님 가이드라인 (본 초안의 기본 원칙, 반드시 반영)
|
||||
1. **재화 사용 시 항상 서버 응답 필수** — 클라 단독 처리 금지
|
||||
2. **미션 클리어 판단은 클라 재량**. 단 **보상 지급 주체(서버 vs 클라)는 결정 필요**. **보상이 재화 형태가 아니면 서버 처리 어려움** 전제
|
||||
3. **랭킹 등록은 클라 정보를 서버가 그대로 저장** — 서버는 검증·계산 없이 저장만. **재화 아닌 랭킹 보상은 서버 처리 불가** 전제
|
||||
|
||||
### 0-2. 현 수상한잡화점 실측 결과
|
||||
|
||||
| 항목 | 실측 내용 |
|
||||
|------|----------|
|
||||
| **서버 스택** | **PlayFab** (Title API + CloudScript + Leaderboard + Profiles + Mail) |
|
||||
| **인증** | `LoginWithCustomID` (기기 기반 익명 ID) |
|
||||
| **저장 방식** | **하이브리드** — `CryptoUtil.Save`로 **로컬 파일 저장이 1차 소스**, PlayFab CloudScript는 핵심 이벤트만 (로그인 토큰·치트·인게임 시작·가이드 퀘스트·우편) |
|
||||
| **AntiCheat** | `CodeStage.AntiCheat.ObscuredTypes` (ObscuredInt/Long, RandomizeCryptoKey) — **로컬 변조 방어 목적**, 서버 검증은 아님 |
|
||||
| **CloudScript 함수 (현행)** | `ServerSave_LoginToken`, `Get_UserInfo`, `Save_CheatItem`, `Save_IngameStart`, `Save_GuideQuest`, `Get_MailList`, `Get_MailReward`, `Get_AllMail`, `Get_OtherUserInfo`, `Del_TitlePlayer` |
|
||||
| **CloudScript 주석처리 (미구현)** | `Save_StageResult` — 스테이지 완료 시 서버 저장. 현재 비활성 |
|
||||
| **랭킹** | `PlayFabProgressionAPI.GetLeaderboard` / `GetLeaderboardAroundEntity` 사용 중 (단, 등록 경로는 소스에서 미확인 — 추가 분석 필요) |
|
||||
| **인앱 결제** | `InappInfo.cs` **전체 주석처리 상태** (현재 비활성). 재활성 시 `ValidateGooglePlayPurchase` 등 PlayFab 검증 경로 복원 필요 |
|
||||
| **주요 저장 데이터 (`ServerData` 클래스)** | StageData, dic_PC, dic_PC_Awakening, Seal(인장), Equipment, dic_Item(재화/소비), dic_Card, EquipmentTrade, CatGoodsShop, Mission(일일/주간), set_PurchasedPCIDs, show_Scenarios, 출석(RewardAttandanceDay), 고양이 레벨(CatLv/CatExp) |
|
||||
|
||||
### 0-3. 미파악·추가 분석 필요 영역 (C23 정직성)
|
||||
- **메타 시스템 SOT 미작성** — 세이브·상점·성장·시즌패스의 공식 설계 문서 부재 (PD #5-B Phase 0-C 미착수)
|
||||
- **랭킹 등록 경로** — `GetLeaderboard` 읽기는 있으나 `UpdatePlayerStatistics`나 CloudScript 등록 호출처가 소스 검색으로 확인 안 됨
|
||||
- **PlayFab 계속 사용 여부** — PD님 결정 필요 (자체 서버 전환 가능성). 본 초안은 **PlayFab 유지 + CloudScript 확장** 전제로 작성
|
||||
|
||||
---
|
||||
|
||||
## A. 서버 관리 데이터 카테고리 (서버 SOT 후보)
|
||||
|
||||
> **범례**: `[확정]` 현 코드 존재 + 재화성 데이터 / `[추가분석]` 현 코드에 있으나 서버 SOT 전환 검토 필요 / `[신규]` 새로 정의 필요
|
||||
|
||||
### A-1. 재화·자산 `[확정]` — 서버 SOT 필수 (PD 가이드 1)
|
||||
|
||||
| 도메인 | 필드 예시 | 비고 |
|
||||
|-------|---------|------|
|
||||
| **재화 인벤토리** | `dic_Item[itemid] = amount` (Dictionary) — Gold·Soul·DailyMissionPoint·WeeklyMissionPoint 등 | 사용 시 항상 서버 응답 필수 |
|
||||
| **PC(캐릭터) 보유** | `dic_PC[pcid]` — 레벨·경험치·각성 | 재화로 육성되므로 서버 SOT |
|
||||
| **장비** | `Equipment` (SD_Equipment) + `EquipmentTrade` | 장비 획득·강화·거래 |
|
||||
| **카드** | `dic_Card[cardid]` (SD_Card) | 카드 수집·업그레이드 |
|
||||
| **구매 PC ID 세트** | `set_PurchasedPCIDs` (HashSet<int>) | 현금·패키지 구매 대상 → 결제 검증 후 서버 저장 |
|
||||
|
||||
### A-2. 진행도·달성 `[확정]`
|
||||
|
||||
| 도메인 | 필드 예시 | 비고 |
|
||||
|-------|---------|------|
|
||||
| **스테이지 진행** | `StageData.dic_stagedata[diff].dic_ChapterData[chapter].list_StageData[].Star` | 난이도·챕터·스테이지별 별 수 |
|
||||
| **최고 도달 스테이지** | `Get_ClearMaxStageDatas()` 계산값 | 서버 저장 SOT (현재 로컬 계산) |
|
||||
| **고양이 레벨** | `CatLv`, `CatExp`, `L_CatLvTime` | 메타 성장 지표 |
|
||||
| **시나리오 시청 기록** | `show_Scenarios` (HashSet<string>) | UX — 서버 저장 권장 (기기 변경 시 재시청 방지) |
|
||||
| **미션 진행** | `Mission.dic_Mission[type]` — 일일/주간 카운트·보상 수령 여부 | 보상이 재화이므로 서버 SOT |
|
||||
| **출석** | `RewardAttandanceDay`, `AttandanceDayOfYear` | 보상이 재화이므로 서버 SOT |
|
||||
|
||||
### A-3. 랭킹 `[추가분석]` (PD 가이드 3)
|
||||
|
||||
| 도메인 | 현 구현 | 필요 조치 |
|
||||
|-------|--------|----------|
|
||||
| **리더보드 읽기** | `PlayFabProgressionAPI.GetLeaderboard` 사용 중 | 유지 |
|
||||
| **리더보드 등록** | 호출처 **미확인** (소스 검색 0건) | **인간 개발자가 기존 등록 경로 재구축 or 신규 설계**. PD 가이드 3에 따라 "클라 정보 그대로 저장" 방식 |
|
||||
| **리더보드 종류** | 현재 함수는 `leaderboard` 문자열 파라미터로 동적 | **리더보드 명세 SOT 필요** — 어떤 랭킹 존재? (스테이지 클리어타임? 최고 도달 스테이지? 고양이 레벨?) 기획팀 정의 필요 |
|
||||
| **랭킹 보상** | **현재 없음** | PD 가이드 3 — **재화 보상만 서버 처리 가능**. 비재화(칭호·스킨 등) 시 PD 결정 필요 |
|
||||
|
||||
### A-4. 세이브 동기화 `[추가분석]` — **가장 중요한 결정 안건**
|
||||
|
||||
현 구조는 **로컬 1차 + 클라우드 보조** 하이브리드. 이를 어디까지 서버 SOT화 할지가 핵심 결정:
|
||||
|
||||
| 데이터 종류 | 현재 | 권장 방향 | 근거 |
|
||||
|-----------|------|----------|------|
|
||||
| 재화·PC·장비·카드 | 로컬 저장 (ObscuredType 난독화) | **서버 SOT** | PD 가이드 1 "재화 사용 항상 서버 응답" |
|
||||
| 스테이지 진행·별 수 | 로컬 저장 | **서버 SOT** (보상 지급 연결) | PD 가이드 2 보상 연결 |
|
||||
| 시나리오 시청 기록 | 로컬 저장 | **서버 저장** (기기 변경 복원) | UX |
|
||||
| UI 옵션·사운드 볼륨 | 로컬 저장 | **로컬 유지** | 서버 저장 불필요 |
|
||||
|
||||
### A-5. 우편 `[확정]`
|
||||
- 현 구현: `Get_MailList`, `Get_MailReward`, `Get_AllMail` CloudScript 존재
|
||||
- 서버 SOT. 재화 보상 우편 → 서버가 수령 처리 + 인벤토리 반영 응답
|
||||
|
||||
### A-6. 시즌·패스 `[신규]` (메타 시스템 — 추가 분석 필요)
|
||||
- **현 SOT 부재**. 기획팀 메타 시스템 SOT 착수 후 구체화
|
||||
- 예상 필드: 현 시즌 ID, 시즌 진행 XP, 패스 보유 여부, 단계별 보상 수령 상태
|
||||
- 시즌 리셋·크로스 시즌 이월은 서버 주도 결정
|
||||
|
||||
### A-7. 상점 `[신규]` (메타 시스템 — 추가 분석 필요)
|
||||
- `CatGoodsShop`(`SD_CatGoodsShop`), `EquipmentTrade`(`SD_EquipmentTrade`) 로컬 구조 존재. **일일 리셋 로직 클라에 있음** (`CheckDailyReset`)
|
||||
- **이슈**: 상점 일일 리셋을 **클라 시간 기반**으로 하면 기기 시간 조작 취약. **서버 시간 기반 리셋 필요**
|
||||
- 현금 상점(패키지·현금 PC 구매)은 A-1의 `set_PurchasedPCIDs`와 연결
|
||||
|
||||
### A-8. 소셜·기타 `[확정 일부]`
|
||||
- **닉네임**: `UpdateUserTitleDisplayName` (PlayFab) — 서버 SOT
|
||||
- **타 유저 정보 조회**: `Get_OtherUserInfo` (CloudScript) — 서버 SOT
|
||||
- **계정 탈퇴**: `Del_TitlePlayer` — 서버 SOT
|
||||
|
||||
---
|
||||
|
||||
## B. 클라/서버 처리 경계 매트릭스
|
||||
|
||||
> **적용 원칙**: PD 가이드 3종을 각 액션에 기계적 적용. 통신 시점·주체·검증 책임 명시.
|
||||
|
||||
### B-1. 재화 관련 액션 (PD 가이드 1 적용 — 서버 응답 필수)
|
||||
|
||||
| 액션 | 클라 역할 | 서버 역할 | 통신 시점 | 비고 |
|
||||
|------|---------|----------|----------|------|
|
||||
| 재화 사용 (예: 카드 업그레이드에 Soul 소비) | 사용 요청 전송 | **잔고 검증·차감·응답** | 요청 즉시 | 서버 거부 시 클라 롤백 |
|
||||
| 재화 획득 (드롭·보상) | 획득 이벤트 통지 | **지급 검증·적립·응답** | 이벤트 발생 즉시 | 스테이지 완료 번들로 묶어 1회 호출 가능 |
|
||||
| 재화 잔고 조회 | UI 표시 | SOT 제공 | 로그인·주요 화면 진입 | 서버 잔고가 정답. 클라 캐시 임시 |
|
||||
| 재화 구매 (현금) | 구매 플로우 실행 | **영수증 검증**(`ValidateGooglePlayPurchase`)·지급 | 영수증 수신 즉시 | InAppInfo 재활성화 필요 |
|
||||
|
||||
### B-2. 미션·퀘스트 (PD 가이드 2 적용 — 클라 판단 + 보상 지급 주체 결정 필요)
|
||||
|
||||
| 액션 | 클라 역할 | 서버 역할 | 통신 시점 | 비고 |
|
||||
|------|---------|----------|----------|------|
|
||||
| 미션 조건 체크 (예: 3회 출석) | **클라 자체 판단** | — | — | 클라가 카운트 누적·달성 판정 |
|
||||
| 미션 완료 통지 | 완료 신호 전송 | 달성 기록 저장 | 완료 즉시 | 단순 기록 |
|
||||
| **미션 보상 (재화)** | 수령 요청 | **지급·응답** | 요청 즉시 | B-1 "재화 획득"과 동일 |
|
||||
| **미션 보상 (비재화)** | **클라 단독 지급** (PC·카드·장비) | 지급 결과만 사후 기록 | 지급 후 통지 | **안건 PD-①** 참조 |
|
||||
|
||||
### B-3. 스테이지 진행 (혼합)
|
||||
|
||||
| 액션 | 클라 역할 | 서버 역할 | 통신 시점 | 비고 |
|
||||
|------|---------|----------|----------|------|
|
||||
| 인게임 시작 | 선택한 맵 전송 | **세션 등록** (`Save_IngameStart` 현 구현) | 전투 시작 시 | 중복 진입 차단·서버 시간 기준 확보 |
|
||||
| 인게임 플레이 | 전 로직 클라 주도 | 개입 없음 | — | 실시간 통신 없음 |
|
||||
| 스테이지 완료 | 결과 전송 (별 수·드롭 아이템·클리어 시간) | **재화 지급 + 진행도 갱신** | 완료 즉시 | `Save_StageResult` 복원·신규 설계 필요 |
|
||||
| 별 보상 수령 (비재화) | 클라 단독 | — | — | B-2 비재화 보상과 동일 |
|
||||
|
||||
### B-4. 랭킹 (PD 가이드 3 적용)
|
||||
|
||||
| 액션 | 클라 역할 | 서버 역할 | 통신 시점 | 비고 |
|
||||
|------|---------|----------|----------|------|
|
||||
| 랭킹 등록 | 점수·메타정보 계산·전송 | **검증 없이 그대로 저장** | 스테이지 완료·시즌 갱신 시 | PD 가이드 3 |
|
||||
| 내 랭킹 조회 | 요청 | `GetLeaderboardAroundEntity` 응답 | 랭킹 UI 진입 | 현 구현 있음 |
|
||||
| 상위 랭킹 조회 | 요청 | `GetLeaderboard` 응답 | 랭킹 UI 진입 | 현 구현 있음 |
|
||||
| **랭킹 보상 (재화)** | 수령 요청 | **지급·응답** | 시즌 종료 시 | B-1과 동일 |
|
||||
| **랭킹 보상 (비재화)** | ? | **서버 처리 불가** | ? | **안건 PD-②** 참조 |
|
||||
|
||||
### B-5. 세이브·메타
|
||||
|
||||
| 액션 | 클라 역할 | 서버 역할 | 통신 시점 | 비고 |
|
||||
|------|---------|----------|----------|------|
|
||||
| 로그인·최초 데이터 로드 | 요청 | 전체 데이터 응답 | 앱 실행 시 | 현 `Get_UserInfo` 구조 확장 |
|
||||
| 주기 세이브 | 변경분 전송 | 저장 | 주요 결정 직후 (결제·전투 완료·재화 변동) | 빈도 최소화, 이벤트 기반 |
|
||||
| 기기 변경 복원 | 로그인 ID 전송 | 동일 계정 데이터 응답 | 신규 기기 로그인 시 | 계정 연결 정책 별도 필요 |
|
||||
| 일일/주간 리셋 | 리셋 UI 표시 | **서버 시간 기반 판정·리셋 응답** | 로그인·일일 최초 접속 | 현재 클라 `DayOfYear` 비교는 취약 |
|
||||
| 시나리오 시청 기록 | 시청 후 통지 | 기록 저장 | 시청 직후 | 기기 변경 시 복원 |
|
||||
|
||||
### B-6. 우편·소셜
|
||||
|
||||
| 액션 | 클라 역할 | 서버 역할 | 통신 시점 | 비고 |
|
||||
|------|---------|----------|----------|------|
|
||||
| 우편 조회 | 요청 | 목록 응답 | 우편함 진입 | 현 구현 있음 |
|
||||
| 우편 수령 (재화 포함) | 수령 요청 | **지급·인벤토리 반영·응답** | 요청 즉시 | 현 구현 있음 |
|
||||
| 닉네임 변경 | 입력값 전송 | 중복 검사·저장 | 변경 시 | 현 구현 있음 |
|
||||
|
||||
---
|
||||
|
||||
## C. 의사결정 필요 항목 (PD님 안건)
|
||||
|
||||
### PD-① 미션·스테이지·업적 보상 지급 주체 (PD 가이드 2 연장)
|
||||
|
||||
**안건**: 비재화 보상(장비·카드·캐릭터 등)의 지급 주체를 어떻게 할 것인가?
|
||||
|
||||
**현 실측 제약**:
|
||||
- `dic_PC`·`dic_Card`·`Equipment` 등 비재화 자산도 **현재 ServerData 로컬 SOT에 포함**
|
||||
- PlayFab CloudScript는 재화 인벤토리 중심, 장비·카드 지급 로직 미구현
|
||||
- PD 전제: "재화 아닌 보상은 서버 처리 어려움"
|
||||
|
||||
**안 2종**:
|
||||
1. **A안 — 클라 단독 지급 + 사후 서버 동기화** (PD 전제 그대로)
|
||||
- 가) 클라가 보상 지급 + ServerData 로컬 갱신
|
||||
- 나) 다음 주기 세이브에서 서버 반영
|
||||
- 다) **리스크**: 지급 직후 앱 강제 종료 시 미반영 — 재지급/유실 판정 정책 필요
|
||||
2. **B안 — 비재화 자산도 서버 SOT 전환** (PD 전제 초과)
|
||||
- 가) 장비·카드·PC 지급 API 신설 (`Grant_Equipment`·`Grant_Card` CloudScript)
|
||||
- 나) 서버가 유니크 인벤토리 관리
|
||||
- 다) **비용**: 개발 공수 + PlayFab 인벤토리 모델 재설계
|
||||
|
||||
**개발팀장 의견**: A안 기본 + 지급 멱등성 보장용 클라 임시 저널(로컬 큐)로 유실 방지. PD 전제 정합.
|
||||
|
||||
---
|
||||
|
||||
### PD-② 비재화 랭킹 보상 처리 방안 (PD 가이드 3 연장)
|
||||
|
||||
**안건**: 랭킹 시즌 종료 시 비재화 보상(칭호·스킨·특수 장비)을 어떻게 지급할 것인가?
|
||||
|
||||
**현 실측 제약**:
|
||||
- 현재 랭킹 등록 경로 미확인 + 랭킹 보상 시스템 자체 미구현
|
||||
- PD 전제: "재화 아닌 랭킹 보상은 서버 처리 불가"
|
||||
|
||||
**안 2종**:
|
||||
1. **A안 — 재화 우편 보상만 운영** (PD 전제 그대로)
|
||||
- 가) 시즌 종료 시 서버가 순위별 재화 우편 발송
|
||||
- 나) 칭호·스킨 등 비재화 보상은 설계에서 제외
|
||||
- 다) **제약**: 랭킹 동기 부여 수단 축소
|
||||
2. **B안 — 클라 단독 지급 + 서버는 "대상자 여부" 플래그만 관리**
|
||||
- 가) 서버: 시즌 종료 시 순위 확정 → 대상자 플래그 저장 (예: `SeasonReward_S03_Tier1: true`)
|
||||
- 나) 클라: 플래그 확인 후 로컬에서 비재화 보상 해금
|
||||
- 다) **리스크**: 플래그 조작 시 부정 해금 — AntiCheat로 일부 방어 가능
|
||||
|
||||
**개발팀장 의견**: B안 권장. 서버 처리 범위를 "대상자 판정"으로 한정하면 PD 전제("재화 아닌 서버 처리 불가") 정신 유지 + 랭킹 유인 확보.
|
||||
|
||||
---
|
||||
|
||||
### PD-③ 서버 스택 유지 여부 (PlayFab vs 자체 서버)
|
||||
|
||||
**안건**: PlayFab을 계속 사용할 것인가, 자체 서버(Node.js + MongoDB 등)로 전환할 것인가?
|
||||
|
||||
**현 상태**: PlayFab 광범위 의존 (인증·CloudScript·Leaderboard·Profiles·Mail·IAP 검증). `PD 지시 #2`에서 서버 Critical 보안 3건 보류 중.
|
||||
|
||||
**안 3종**:
|
||||
1. **A안 — PlayFab 유지 + CloudScript 확장** (현재 기조 유지)
|
||||
2. **B안 — PlayFab 인증·Leaderboard만 유지 + 게임 로직은 자체 서버**
|
||||
3. **C안 — 완전 자체 서버 전환**
|
||||
|
||||
**개발팀장 의견**: 본 안건은 **인간 서버 개발자 배정 후 해당 개발자의 기술 스택 선호·경험과 함께 결정 권장**. 본 초안은 A안(PlayFab 유지) 전제로 작성되었으나, 개발자 배정 시 재검토 필요.
|
||||
|
||||
---
|
||||
|
||||
### PD-④ 세이브 SOT 전환 범위
|
||||
|
||||
**안건**: 현재 **로컬 1차 + 클라우드 보조** 하이브리드를 **서버 1차 SOT**로 전환할 것인가?
|
||||
|
||||
**안 2종**:
|
||||
1. **A안 — 현 하이브리드 유지** — 최소 변경. 재화·결제만 서버 검증
|
||||
2. **B안 — 서버 1차 SOT 전환** — 로컬은 오프라인 캐시로만. 모든 변경 서버 저장
|
||||
|
||||
**개발팀장 의견**: **B안이 원칙적으로 옳으나 수상한잡화점 현 시점에는 A안 유지 권장**. 이유: 서버 Critical 3건 보류 중 + 게임 오프라인 플레이 비중 고려. 차기 프로젝트는 B안 기반 설계 (헌법 제1원칙 목표 2 — 코어 프레임워크에 반영 권장).
|
||||
|
||||
---
|
||||
|
||||
### PD-⑤ 일일/주간 리셋 시간 기준
|
||||
|
||||
**안건**: 현재 `DayOfYear` 클라 시간 비교 방식을 서버 시간 기반으로 전환할 것인가?
|
||||
|
||||
**현 이슈**: 기기 시간 조작으로 일일 출석·일일 상점 어뷰즈 가능.
|
||||
|
||||
**개발팀장 의견**: **서버 시간 기준 전환 필수**. PD 재검토 불요 수준의 보안 기본. 서버 Critical 보안 3건 재개 시 포함 권장.
|
||||
|
||||
---
|
||||
|
||||
## D. 인간 서버 개발자 착수 시 선행 작업 (C30·P13·P18 정합)
|
||||
|
||||
1. **선행 Read 패키지** (서버팀장 사전 보고 서버 재개 예비 C-1 적용)
|
||||
- 가) 본 문서
|
||||
- 나) `공유/PD_지시_트래킹/개발팀_PD_지시_로그.md` #2 (서버 Critical 3건)
|
||||
- 다) 현 `ServerClass.cs`·`ServerInfo.cs` 전문
|
||||
- 라) `memory/org/feedback_*.md` 중 서버·보안 관련
|
||||
|
||||
2. **환경 셋업**
|
||||
- 가) PlayFab 계정·TitleId 확인 (현 코드 `Server_Live_ID`·`Server_Dev_ID` 상수 주석 처리 상태)
|
||||
- 나) CloudScript 현 코드 dump 및 버전 관리 편입
|
||||
- 다) Unity 클라이언트 개발 환경 (수상한잡화점 브랜치 pull)
|
||||
|
||||
3. **설계 문서화 의무 (P18)**
|
||||
- 가) 본 초안의 PD 결정 후 **서버 아키텍처 공식 설계 문서** 작성 (`프로젝트/수상한잡화점/서버/01_서버_아키텍처_v1.md` 신규)
|
||||
- 나) API 계약서 작성 (`02_API_계약서_v1.md`) — 엔드포인트·파라미터·응답·에러 코드
|
||||
- 다) CloudScript 함수 명세 (`03_CloudScript_명세_v1.md`)
|
||||
|
||||
4. **P13 공용 인터페이스 변경 사전 공유**
|
||||
- 가) 클라이언트팀(현 개발팀장)과 데이터 포맷 협의
|
||||
- 나) 기획팀과 재화·미션·랭킹 밸런스 협의
|
||||
|
||||
---
|
||||
|
||||
## E. 산출물 요약
|
||||
|
||||
| 분류 | 건수 | 긴급도 |
|
||||
|------|------|--------|
|
||||
| A. 데이터 카테고리 | 8개 도메인 (재화·진행도·랭킹·세이브·우편·시즌·상점·소셜) | 정리 완료 |
|
||||
| B. 클라/서버 매트릭스 | 6개 액션군 × 약 25건 | 정리 완료 |
|
||||
| C. PD 결정 안건 | 5건 (보상 지급 주체·비재화 랭킹·서버 스택·SOT 범위·리셋 시간) | PD님 결정 대기 |
|
||||
| D. 선행 작업 | 4단 | 인간 개발자 배정 시 적용 |
|
||||
|
||||
---
|
||||
|
||||
## F. 기각안 (P24·P27 개정 — 결정·설계 엔트리 필수)
|
||||
|
||||
본 초안이 채택하지 않은 대안들:
|
||||
|
||||
1. **"서버 처리를 최소화하고 전부 클라 주도로" 안** — PD 가이드 1(재화 서버 필수)과 충돌. 어뷰즈 방어 근거 소멸. 기각
|
||||
2. **"모든 비재화 자산을 서버 SOT로 전환" 안** — PD 전제("재화 아닌 서버 처리 어려움") 초과. 개발 공수 큼. PD-① B안으로만 옵션 제시하고 초안 본문에서는 비채택
|
||||
3. **"PlayFab 전면 폐기 + 자체 서버 전면 신규" 안** — 서버 Critical 3건 보류 상태에서 범위 확대는 리스크. PD-③에 옵션으로만 제시하고 본 초안은 PlayFab 유지 전제
|
||||
4. **"인간 개발자 배정 전 세부 API 스펙까지 확정" 안** — C9(완성도 우선) 정신이나, **배정된 개발자의 기술 선호·경험 반영이 없는 API 스펙은 재작업 리스크**. 본 초안은 원칙·경계·안건까지 정리하고 세부 스펙은 개발자 배정 후
|
||||
|
||||
---
|
||||
|
||||
## G. PM 처리 권장
|
||||
|
||||
1. **PD님 보고**: 본 초안 + PD-①~⑤ 5건 안건 (C29-3 목표 3 정신 — 결정 요청 최소화된 형태)
|
||||
2. **PD 결정 수령 시 후속**: 개발팀 PD 지시 로그 #2 재개 트리거 조건 명확화 (서버 Critical 3건 + 본 초안 PD 결정 연계)
|
||||
3. **인간 개발자 배정 시**: 본 초안 + D-1 선행 Read 패키지 동봉하여 온보딩
|
||||
4. **차기 프로젝트 코어 프레임워크 반영**: PD-④ B안(서버 1차 SOT), PD-⑤(서버 시간 기준)는 헌법 제1원칙 목표 2 — 차기 프로젝트 프레임워크 기본 탑재 권장
|
||||
|
||||
---
|
||||
|
||||
**서명**: 개발팀장 (서버팀장·클라이언트팀장 Agent 호출 환경 제약으로 단독 분석, C23 정직성 준수)
|
||||
**검증 권장**: dev-auditor 모드 A 1회 + PM 교차 검토 후 PD 상신
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
---
|
||||
from: plan-auditor
|
||||
to: PM
|
||||
type: 감사보고
|
||||
subject: 기획팀장 재량작업 3건(#33·#34·#35) + 기획 결정 2건(#30·#31) 모드 B 주기 감사
|
||||
priority: MID
|
||||
status: 완료
|
||||
created: 2026-04-17
|
||||
related_pd_지시: 기획팀 #30·#31·#33·#34·#35
|
||||
related_commit: 06a7bb8
|
||||
---
|
||||
|
||||
# plan-auditor 모드 B 감사 보고
|
||||
|
||||
## 1. 감사 범위
|
||||
- PD 지시 #33 REQ 템플릿, #34 에이전트 6종 기록 의무, #35 밸런싱 md 4종 변경 이력
|
||||
- 연관 #30(맥락 오류 점검)·#31(P24 기각안 필수화) 정합성 교차
|
||||
- 조직운영 대화로그·수상한잡화점 대화로그·소통 채널·PD 지시 로그 실측
|
||||
|
||||
## 2. 실측 근거 (tool_use 입증)
|
||||
| 항목 | 결과 |
|
||||
|---|---|
|
||||
| `REQ-템플릿_밸런스수치.md` | 존재, 5923 bytes, YAML 프론트매터 7필드 이상 포함 |
|
||||
| 밸런싱 md 4종 "변경 이력 (P16 산출물 추적성)" 섹션 | 4종 전부 존재 (line 313·168·587·378) |
|
||||
| 에이전트 6종 (balance/content/level/narrative/system/ux) | 6종 전부 "기록 의무"·"plan-auditor"·"기각안 필드 필수" 키워드 각 4회 매칭 (일관 구조) |
|
||||
| 기획팀 PD 지시 로그 #33·#34·#35 | 완료 아카이브 이동 완료, 산출물 경로 실존 |
|
||||
| 조직운영 대화로그 2026-04-17 기획팀장 엔트리 | 기각안 3건 명시(P24 신규 필수화 요구 충족) |
|
||||
| 수상한잡화점 2026-04-17 대화로그 #33·#34·#35 엔트리 | **부재** (조직운영 로그에만 기록) |
|
||||
|
||||
## 3. 분류 결과
|
||||
|
||||
### Critical — 없음
|
||||
|
||||
### Major — 1건
|
||||
**M1. 수상한잡화점 대화로그 누락 (P24·P27-4 SOT 경계 혼선)**
|
||||
- 현상: #33·#34·#35는 수상한잡화점 프로젝트 산출물(REQ·밸런싱 md) 영향이 직접적이나 `공유/대화로그/수상한잡화점/2026-04-17.md`에 기획팀장 3건 일괄 엔트리가 없음. 조직운영 로그에만 기록.
|
||||
- 영향: 프로젝트별 대화로그로 차기 프로젝트 참고 자료를 추출할 때 수상한잡화점 파일에 기획팀 변경 맥락 누락 → 목표 2 원칙 B 인사이트 소실 위험.
|
||||
- 정정 방안: `공유/대화로그/수상한잡화점/2026-04-17.md`에 1건 append (요지·이유·산출물·상태·기각안 3종 요약, 조직운영 엔트리 교차 참조).
|
||||
|
||||
### Minor — 1건
|
||||
**m1. REQ 템플릿 "관련PD지시" 필드는 선택이나, 템플릿 본문에 "기각안 필드 예시 값" 미제공**
|
||||
- P24 개정 직후 신설이므로 차기 REQ 작성자가 공란 혼선 위험.
|
||||
- 정정 방안: 템플릿 "기각안" 섹션에 예시 1행 추가(선택적 권고).
|
||||
|
||||
### Improvement — 2건
|
||||
**I1. 밸런싱 md 변경 이력 "기각안" 컬럼 부재**
|
||||
- 현재 포맷: 일시/변경자/변경 필드/이전값→이후값/재미 근거/관련 PD 지시#. P24 필수화가 결정·설계 엔트리 범위로 밸런스 수치 변경도 해당. 컬럼 1개 추가 권고.
|
||||
|
||||
**I2. 기획팀장 2026-04-17_업무공유체계_점검_기획팀.md 안건 1이 P24 개정으로 종결되었는데, 소통 파일 status가 "완료"로 이동·표기되었는지 미확인**
|
||||
- PM이 `공유/소통/완료/` 이동 운영 원칙(C29-4) 확인 필요.
|
||||
|
||||
## 4. 규칙 준수 스코어
|
||||
| 규칙 | 준수 | 비고 |
|
||||
|---|---|---|
|
||||
| C5 정직성 | O | 기각안 3종 명시(기획팀장 엔트리) |
|
||||
| C6 데이터 보호 | O (해당 없음 — 백업 대상 xlsm 변경 없음) | — |
|
||||
| C7 재미 우선 | O | 변경 이력 "재미 근거" 필드 표준화 |
|
||||
| C13·C27·C29-4 | O | PD 지시 로그 활성→아카이브 이동 완료 |
|
||||
| P16 추적성 | O | 4종 테이블 신설 |
|
||||
| P17 배타 배치 | 해당 없음 | — |
|
||||
| P18 설계 문서화 | O | 참조된 문서 전부 실존 |
|
||||
| **P24 기각안 필수** | **부분 O** | 조직운영 엔트리 충족, 수상한잡화점 엔트리 부재(M1) |
|
||||
| P27-2 호출 프롬프트 3요소 | 해당 없음 (기획팀장이 Agent 재호출 불가) | — |
|
||||
| P27-4 SOT 경계 | 부분 O | 프로젝트 축 누락(M1) |
|
||||
|
||||
## 5. PM 요청 조치
|
||||
1. M1 즉시 시정 — `공유/대화로그/수상한잡화점/2026-04-17.md` 기획팀장 3건 일괄 엔트리 append (PM 재량 처리 권고)
|
||||
2. I1 검토 후 밸런싱 md 4종 변경 이력 포맷에 "기각안" 컬럼 추가 여부 결정 (기획팀장에게 차기 위임)
|
||||
3. I2 `공유/소통/` 완료 이동 운영 일괄 점검 (pm-auditor 감사 C1과 동일 패턴 — 중복 Major 방지)
|
||||
|
||||
## 6. 노하우 축적 (차기 프로젝트 참고)
|
||||
- **기획 결정의 기각안은 조직운영 로그에 집중되는 경향** — 수상한잡화점 프로젝트 로그에도 동시 기록이 필요 (P27-4 양방향 SOT 유지)
|
||||
- **밸런싱 md 변경 이력 테이블은 인라인 기록이 누락 방지에 효과적** — 기획팀장 기각안 3(인라인 유지) 타당성 실증
|
||||
- **전문 에이전트 6종의 공통 지침은 SKILL.md 참조형 + 영역 특화 인라인 혼합**이 최적(C14-4 vs 체감 준수율 균형) — 기획팀장 기각안 2 타당성 실증
|
||||
|
|
@ -8,6 +8,19 @@
|
|||
|
||||
### Added
|
||||
- 패키지 스켈레톤 (폴더 구조, asmdef, package.json)
|
||||
- Tier 1 기반 Core 4종: `Log`, `CoroutineRunner`, `MonoSingleton`, `ServiceLocator` (+ 테스트 28건)
|
||||
- Tier 1 Attribute 3종 (2026-04-17)
|
||||
- `NerdNavis.Core.Attribute.ReadOnlyAttribute` — 인스펙터 읽기 전용
|
||||
- `NerdNavis.Core.Attribute.ShowIfAttribute` — 조건부 인스펙터 노출
|
||||
- `NerdNavis.Core.Attribute.ArrayTitleAttribute` — 배열 요소 라벨 커스터마이즈
|
||||
- Tier 1 Util 6종 (2026-04-17)
|
||||
- `NerdNavis.Core.Util.EnumToInt` — 박싱-프리 enum ↔ int 변환
|
||||
- `NerdNavis.Core.Util.EnumEx` — enum 메타데이터·파싱 유틸 (캐시 기반)
|
||||
- `NerdNavis.Core.Util.FormatEx` — 수치 축약·시간·확률·바이트 포맷
|
||||
- `NerdNavis.Core.Util.MathEx` — Clamp·Remap·SmoothTowards·GCD/LCM 등
|
||||
- `NerdNavis.Core.Util.KeyMaker` — `':'` 구분자 합성 키 생성
|
||||
- `NerdNavis.Core.Util.ValidationEx` — 인자·상태 가드(NotNull·InRange·Positive 등)
|
||||
- 위 9종 단위 테스트 추가 (NUnit, Tests/Runtime/Core/Util + Attribute)
|
||||
|
||||
## [0.1.0] - TBD
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,54 @@
|
|||
// ---------------------------------------------------------------------------
|
||||
// NerdNavis.Framework
|
||||
// ArrayTitleAttribute.cs — 배열 요소별 인스펙터 라벨 커스터마이즈 속성
|
||||
// ---------------------------------------------------------------------------
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NerdNavis.Core.Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// 배열·리스트의 각 요소를 인스펙터에서 표시할 때
|
||||
/// "Element N" 대신 지정 멤버의 값을 라벨로 사용하도록 지시한다.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>설계 문서 §5 Tier 1. 데이터 테이블·카드 리스트 등 요소 식별이 잦은 컬렉션을
|
||||
/// 인스펙터에서 빠르게 탐색할 수 있도록 한다.</para>
|
||||
/// <para>동작:</para>
|
||||
/// <list type="bullet">
|
||||
/// <item><description>요소 타입 내부의 <see cref="MemberName"/> 필드 또는 프로퍼티를 읽어
|
||||
/// <see cref="object.ToString"/> 결과를 라벨로 사용.</description></item>
|
||||
/// <item><description>해당 멤버를 찾지 못하면 기본 "Element N" 라벨로 폴백.</description></item>
|
||||
/// <item><description><see cref="Prefix"/>가 지정된 경우 접두어로 덧붙인다(예: "Card: Fireball").</description></item>
|
||||
/// </list>
|
||||
/// <para>실제 그리기는 Editor 스크립트 <c>ArrayTitleDrawer</c>에서 담당한다.</para>
|
||||
/// <para>사용 예:</para>
|
||||
/// <code>
|
||||
/// [SerializeField, ArrayTitle(nameof(CardEntry.DisplayName), "Card")]
|
||||
/// private List<CardEntry> _cards;
|
||||
/// </code>
|
||||
/// </remarks>
|
||||
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = true)]
|
||||
public sealed class ArrayTitleAttribute : PropertyAttribute
|
||||
{
|
||||
/// <summary>요소 내부에서 라벨로 사용할 멤버 이름.</summary>
|
||||
public string MemberName { get; }
|
||||
|
||||
/// <summary>라벨 접두어(옵션). 빈 문자열이면 접두어 없이 멤버 값만 표시.</summary>
|
||||
public string Prefix { get; }
|
||||
|
||||
/// <summary>기본 생성자. 접두어 없이 멤버 값만 라벨로 사용한다.</summary>
|
||||
public ArrayTitleAttribute(string memberName)
|
||||
{
|
||||
MemberName = memberName;
|
||||
Prefix = string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>접두어와 함께 라벨을 구성한다. 표시 형식: "{Prefix}: {MemberValue}".</summary>
|
||||
public ArrayTitleAttribute(string memberName, string prefix)
|
||||
{
|
||||
MemberName = memberName;
|
||||
Prefix = prefix ?? string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
// ---------------------------------------------------------------------------
|
||||
// NerdNavis.Framework
|
||||
// ReadOnlyAttribute.cs — 인스펙터 읽기 전용 표시 속성
|
||||
// ---------------------------------------------------------------------------
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NerdNavis.Core.Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// 필드를 Unity 인스펙터에서 읽기 전용으로 표시하도록 지시한다.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>설계 문서 §5 Tier 1. 기존 NerdNavisCore <c>DG.Core.Attribute.ReadOnlyAttribute</c>
|
||||
/// 구조를 승계하되 네임스페이스만 <see cref="NerdNavis.Core.Attribute"/>로 이전했다.</para>
|
||||
/// <para>실제 그리기(GUI.enabled=false)는 Editor 스크립트 <c>ReadOnlyDrawer</c>에서 수행한다.</para>
|
||||
/// <para>사용 예:</para>
|
||||
/// <code>
|
||||
/// [SerializeField, ReadOnly] private int _runtimeOnly;
|
||||
/// </code>
|
||||
/// <para>주의: 런타임 직렬화 동작에는 영향을 주지 않으며 인스펙터 편집 차단만 담당한다.</para>
|
||||
/// </remarks>
|
||||
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = true)]
|
||||
public sealed class ReadOnlyAttribute : PropertyAttribute
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
// ---------------------------------------------------------------------------
|
||||
// NerdNavis.Framework
|
||||
// ShowIfAttribute.cs — 조건부 인스펙터 노출 속성
|
||||
// ---------------------------------------------------------------------------
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NerdNavis.Core.Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// 동일 오브젝트의 다른 필드·프로퍼티 값이 <see cref="ExpectedValue"/>와 일치할 때만
|
||||
/// 인스펙터에 필드를 표시한다.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>설계 문서 §5 Tier 1. 기존 코어의 조건부 노출 기능을 범용화했다.</para>
|
||||
/// <para>비교 규칙:</para>
|
||||
/// <list type="bullet">
|
||||
/// <item><description><see cref="ExpectedValue"/>가 <c>null</c>이면 대상 멤버의 truthy 여부로 판정
|
||||
/// (bool은 자기자신, 숫자는 0이 아닐 때, 참조는 non-null 일 때 true).</description></item>
|
||||
/// <item><description>값 비교는 <see cref="object.Equals(object)"/> 로 수행한다.</description></item>
|
||||
/// <item><description>대상 멤버가 없거나 접근 불가면 필드는 기본 노출된다(Editor 오류 대신 안전한 기본값).</description></item>
|
||||
/// </list>
|
||||
/// <para>실제 그리기는 Editor 스크립트 <c>ShowIfDrawer</c>에서 담당하며, 런타임 동작에는 영향을 주지 않는다.</para>
|
||||
/// <para>사용 예:</para>
|
||||
/// <code>
|
||||
/// [SerializeField] private bool _useCustomRange;
|
||||
/// [SerializeField, ShowIf(nameof(_useCustomRange))] private Vector2 _range;
|
||||
/// </code>
|
||||
/// </remarks>
|
||||
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = true)]
|
||||
public sealed class ShowIfAttribute : PropertyAttribute
|
||||
{
|
||||
/// <summary>감시할 멤버(필드·프로퍼티) 이름.</summary>
|
||||
public string MemberName { get; }
|
||||
|
||||
/// <summary>비교 대상 값. <c>null</c>이면 truthy 비교를 수행한다.</summary>
|
||||
public object ExpectedValue { get; }
|
||||
|
||||
/// <summary>조건 불일치 시 인스펙터에서 완전히 숨길지 여부. <c>false</c>면 비활성화만.</summary>
|
||||
public bool HideInInspector { get; }
|
||||
|
||||
/// <summary>단순 truthy 조건을 설정한다.</summary>
|
||||
public ShowIfAttribute(string memberName, bool hideInInspector = true)
|
||||
{
|
||||
MemberName = memberName;
|
||||
ExpectedValue = null;
|
||||
HideInInspector = hideInInspector;
|
||||
}
|
||||
|
||||
/// <summary>특정 값과 일치할 때만 노출한다.</summary>
|
||||
public ShowIfAttribute(string memberName, object expectedValue, bool hideInInspector = true)
|
||||
{
|
||||
MemberName = memberName;
|
||||
ExpectedValue = expectedValue;
|
||||
HideInInspector = hideInInspector;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
// ---------------------------------------------------------------------------
|
||||
// NerdNavis.Framework
|
||||
// EnumEx.cs — enum 메타데이터·열거·변환 확장 유틸리티
|
||||
// ---------------------------------------------------------------------------
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace NerdNavis.Core.Util
|
||||
{
|
||||
/// <summary>
|
||||
/// <c>enum</c> 메타데이터(정의된 값 목록·최소/최대·TryParse 등)를 다루는 확장 유틸리티.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>설계 문서 §5 Tier 1. <see cref="Enum.GetValues(Type)"/> 는 매 호출 시 배열을 새로 할당하므로,
|
||||
/// 본 유틸은 타입별로 값 배열을 캐시하여 재사용한다(C11 자원 효율성).</para>
|
||||
/// <para>제공 기능:</para>
|
||||
/// <list type="bullet">
|
||||
/// <item><description><see cref="GetValues{TEnum}"/>: 정의된 값의 읽기 전용 배열(캐시된 인스턴스).</description></item>
|
||||
/// <item><description><see cref="GetNames{TEnum}"/>: 정의된 이름의 읽기 전용 배열(캐시).</description></item>
|
||||
/// <item><description><see cref="IsDefined{TEnum}(TEnum)"/>: 정의된 값인지 박싱 없이 확인.</description></item>
|
||||
/// <item><description><see cref="TryParse{TEnum}"/>: 대소문자 무시 파싱.</description></item>
|
||||
/// <item><description><see cref="Count{TEnum}"/>: 정의된 값 개수.</description></item>
|
||||
/// </list>
|
||||
/// </remarks>
|
||||
public static class EnumEx
|
||||
{
|
||||
private static class Cache<TEnum> where TEnum : struct, Enum
|
||||
{
|
||||
public static readonly TEnum[] Values;
|
||||
public static readonly string[] Names;
|
||||
public static readonly HashSet<int> DefinedInts;
|
||||
|
||||
static Cache()
|
||||
{
|
||||
Values = (TEnum[])Enum.GetValues(typeof(TEnum));
|
||||
Names = Enum.GetNames(typeof(TEnum));
|
||||
DefinedInts = new HashSet<int>();
|
||||
for (int i = 0; i < Values.Length; i++)
|
||||
{
|
||||
DefinedInts.Add(EnumToInt.Convert(Values[i]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>정의된 enum 값을 배열로 반환한다(캐시된 인스턴스, 수정 금지).</summary>
|
||||
public static TEnum[] GetValues<TEnum>() where TEnum : struct, Enum
|
||||
{
|
||||
return Cache<TEnum>.Values;
|
||||
}
|
||||
|
||||
/// <summary>정의된 enum 이름을 배열로 반환한다(캐시된 인스턴스, 수정 금지).</summary>
|
||||
public static string[] GetNames<TEnum>() where TEnum : struct, Enum
|
||||
{
|
||||
return Cache<TEnum>.Names;
|
||||
}
|
||||
|
||||
/// <summary>정의된 enum 값의 개수를 반환한다.</summary>
|
||||
public static int Count<TEnum>() where TEnum : struct, Enum
|
||||
{
|
||||
return Cache<TEnum>.Values.Length;
|
||||
}
|
||||
|
||||
/// <summary>값이 enum에 정의되어 있는지 확인한다(박싱 없음).</summary>
|
||||
/// <remarks><see cref="Enum.IsDefined(Type, object)"/> 는 박싱·선형 탐색을 하지만
|
||||
/// 본 메서드는 <see cref="HashSet{T}"/> 조회로 O(1) 처리한다.</remarks>
|
||||
public static bool IsDefined<TEnum>(TEnum value) where TEnum : struct, Enum
|
||||
{
|
||||
return Cache<TEnum>.DefinedInts.Contains(EnumToInt.Convert(value));
|
||||
}
|
||||
|
||||
/// <summary>문자열을 enum으로 파싱한다.</summary>
|
||||
/// <param name="name">enum 값 이름.</param>
|
||||
/// <param name="ignoreCase">대소문자 무시 여부(기본 <c>true</c>).</param>
|
||||
/// <param name="result">파싱 성공 시 값, 실패 시 <c>default</c>.</param>
|
||||
/// <returns>성공 시 <c>true</c>.</returns>
|
||||
public static bool TryParse<TEnum>(string name, bool ignoreCase, out TEnum result)
|
||||
where TEnum : struct, Enum
|
||||
{
|
||||
if (string.IsNullOrEmpty(name))
|
||||
{
|
||||
result = default;
|
||||
return false;
|
||||
}
|
||||
return Enum.TryParse(name, ignoreCase, out result);
|
||||
}
|
||||
|
||||
/// <summary>대소문자를 무시하여 파싱한다(편의 오버로드).</summary>
|
||||
public static bool TryParse<TEnum>(string name, out TEnum result) where TEnum : struct, Enum
|
||||
{
|
||||
return TryParse(name, true, out result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
// ---------------------------------------------------------------------------
|
||||
// NerdNavis.Framework
|
||||
// EnumToInt.cs — boxing-free enum ↔ int 변환 유틸리티
|
||||
// ---------------------------------------------------------------------------
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace NerdNavis.Core.Util
|
||||
{
|
||||
/// <summary>
|
||||
/// <c>enum</c> 값을 <see cref="int"/>로 변환할 때 발생하는 박싱을 회피하는 유틸리티.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>설계 문서 §5 Tier 1 (C11 자원 효율성). <c>Convert.ToInt32((object)e)</c> 또는
|
||||
/// <c>(int)(object)e</c> 는 박싱을 유발하지만, <c>Unsafe.As<TEnum, int></c> 를 이용하면
|
||||
/// JIT 단계에서 직접 변환되어 힙 할당이 전혀 발생하지 않는다.</para>
|
||||
/// <para>사용 전제:</para>
|
||||
/// <list type="bullet">
|
||||
/// <item><description>대상 enum의 기저 타입이 <see cref="int"/>여야 한다(<see cref="long"/>·<see cref="byte"/>는 미지원).</description></item>
|
||||
/// <item><description>hot-path(매 프레임 수백~수천 회 호출)에서 사용해 효과가 크다.</description></item>
|
||||
/// <item><description>Dictionary·HashSet의 key로 enum을 쓸 때는 별도 <c>IEqualityComparer</c>로 감싸서 박싱을 함께 회피한다.</description></item>
|
||||
/// </list>
|
||||
/// <para>사용 예:</para>
|
||||
/// <code>
|
||||
/// int id = EnumToInt.Convert(CardType.Attack);
|
||||
/// CardType t = EnumToInt.FromInt<CardType>(id);
|
||||
/// </code>
|
||||
/// </remarks>
|
||||
public static class EnumToInt
|
||||
{
|
||||
/// <summary>enum을 int로 변환한다(박싱 없음).</summary>
|
||||
/// <typeparam name="TEnum">기저 타입 <see cref="int"/>인 enum.</typeparam>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int Convert<TEnum>(TEnum value) where TEnum : struct, Enum
|
||||
{
|
||||
return Unsafe.As<TEnum, int>(ref value);
|
||||
}
|
||||
|
||||
/// <summary>int 값을 enum으로 변환한다(박싱 없음).</summary>
|
||||
/// <typeparam name="TEnum">기저 타입 <see cref="int"/>인 enum.</typeparam>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static TEnum FromInt<TEnum>(int value) where TEnum : struct, Enum
|
||||
{
|
||||
return Unsafe.As<int, TEnum>(ref value);
|
||||
}
|
||||
|
||||
/// <summary>두 enum 값의 동등성을 박싱 없이 비교한다.</summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool Equals<TEnum>(TEnum a, TEnum b) where TEnum : struct, Enum
|
||||
{
|
||||
return Unsafe.As<TEnum, int>(ref a) == Unsafe.As<TEnum, int>(ref b);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
// ---------------------------------------------------------------------------
|
||||
// NerdNavis.Framework
|
||||
// FormatEx.cs — 게임 빈출 포맷(수치 축약·시간·확률) 유틸리티
|
||||
// ---------------------------------------------------------------------------
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
|
||||
namespace NerdNavis.Core.Util
|
||||
{
|
||||
/// <summary>
|
||||
/// 게임에서 자주 쓰이는 수치·시간·확률 포맷 변환을 모은 정적 유틸리티.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>설계 문서 §5 Tier 1. 로컬라이제이션 컨텍스트가 없는 순수 수치 포맷만 담당한다.
|
||||
/// 언어별 라벨(예: "골드")은 <c>NerdNavis.Localization</c>에서 별도로 처리한다.</para>
|
||||
/// <para>Invariant 문화권을 사용해 디바이스 로케일과 무관한 일관 출력 보장(C11 코드 범용성).</para>
|
||||
/// </remarks>
|
||||
public static class FormatEx
|
||||
{
|
||||
private static readonly string[] s_AbbrevSuffixes = { "", "K", "M", "B", "T" };
|
||||
|
||||
/// <summary>큰 수를 K/M/B/T 단위로 축약한다.</summary>
|
||||
/// <param name="value">원본 정수.</param>
|
||||
/// <param name="decimals">소수 자리수(기본 1).</param>
|
||||
/// <returns>예: 12345 → "12.3K", 1_500_000 → "1.5M".</returns>
|
||||
public static string AbbreviateNumber(long value, int decimals = 1)
|
||||
{
|
||||
if (value == 0) return "0";
|
||||
|
||||
long abs = value < 0 ? -value : value;
|
||||
int tier = 0;
|
||||
double scaled = abs;
|
||||
while (scaled >= 1000.0 && tier < s_AbbrevSuffixes.Length - 1)
|
||||
{
|
||||
scaled /= 1000.0;
|
||||
tier++;
|
||||
}
|
||||
|
||||
string sign = value < 0 ? "-" : string.Empty;
|
||||
if (tier == 0)
|
||||
{
|
||||
return sign + abs.ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
string format = decimals > 0 ? "F" + decimals : "F0";
|
||||
return sign + scaled.ToString(format, CultureInfo.InvariantCulture) + s_AbbrevSuffixes[tier];
|
||||
}
|
||||
|
||||
/// <summary>초 단위 시간을 "mm:ss" 또는 "hh:mm:ss"로 포맷한다.</summary>
|
||||
public static string FormatTimeSpan(float seconds)
|
||||
{
|
||||
if (seconds < 0f) seconds = 0f;
|
||||
int total = (int)seconds;
|
||||
int h = total / 3600;
|
||||
int m = (total % 3600) / 60;
|
||||
int s = total % 60;
|
||||
|
||||
if (h > 0)
|
||||
{
|
||||
return string.Format(CultureInfo.InvariantCulture, "{0:D2}:{1:D2}:{2:D2}", h, m, s);
|
||||
}
|
||||
return string.Format(CultureInfo.InvariantCulture, "{0:D2}:{1:D2}", m, s);
|
||||
}
|
||||
|
||||
/// <summary>0~1 범위의 확률을 퍼센트 문자열로 변환한다. 예: 0.1234 → "12.34%".</summary>
|
||||
public static string FormatPercent(float ratio01, int decimals = 2)
|
||||
{
|
||||
float pct = ratio01 * 100f;
|
||||
string format = "F" + decimals;
|
||||
return pct.ToString(format, CultureInfo.InvariantCulture) + "%";
|
||||
}
|
||||
|
||||
/// <summary>수치 사이에 천 단위 구분자를 삽입한다(Invariant, 쉼표).</summary>
|
||||
public static string WithThousands(long value)
|
||||
{
|
||||
return value.ToString("N0", CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
/// <summary>바이트 크기를 B/KB/MB/GB 로 축약한다(Base 1024).</summary>
|
||||
public static string FormatBytes(long bytes, int decimals = 2)
|
||||
{
|
||||
if (bytes < 0) return "-" + FormatBytes(-bytes, decimals);
|
||||
string[] units = { "B", "KB", "MB", "GB", "TB" };
|
||||
double value = bytes;
|
||||
int idx = 0;
|
||||
while (value >= 1024.0 && idx < units.Length - 1)
|
||||
{
|
||||
value /= 1024.0;
|
||||
idx++;
|
||||
}
|
||||
string format = idx == 0 ? "F0" : ("F" + decimals);
|
||||
return value.ToString(format, CultureInfo.InvariantCulture) + units[idx];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
// ---------------------------------------------------------------------------
|
||||
// NerdNavis.Framework
|
||||
// KeyMaker.cs — Dictionary·PlayerPrefs 키 조합 생성 유틸리티
|
||||
// ---------------------------------------------------------------------------
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace NerdNavis.Core.Util
|
||||
{
|
||||
/// <summary>
|
||||
/// Dictionary·PlayerPrefs·SaveData 등에서 사용할 합성 키를 일관된 규칙으로 생성하는 유틸리티.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>설계 문서 §5 Tier 1. 수상한 잡화점에서 `$"{category}_{id}"` 와 `$"{category}:{id}"`
|
||||
/// 가 혼재되어 키 충돌·조회 실패가 발생한 경험을 반영, 프레임워크 전체에서 구분자 <c>':'</c>로 고정한다.</para>
|
||||
/// <para>구분자 <c>':'</c>를 선택한 이유:</para>
|
||||
/// <list type="bullet">
|
||||
/// <item><description>JSON·URL 경로·Redis 네임스페이스 등 외부 시스템 관례와 일치.</description></item>
|
||||
/// <item><description>숫자 ID·GUID 등에 포함되지 않아 파싱 안전.</description></item>
|
||||
/// <item><description>PlayerPrefs 키로 안전하게 사용 가능.</description></item>
|
||||
/// </list>
|
||||
/// <para>주의: 이미 구분자를 포함한 값이 인자로 들어와도 이스케이프하지 않는다.
|
||||
/// 키 역파싱(<see cref="SplitRoot"/>)이 필요한 경우 호출자가 입력을 검증한다.</para>
|
||||
/// </remarks>
|
||||
public static class KeyMaker
|
||||
{
|
||||
/// <summary>합성 키에 사용되는 구분자.</summary>
|
||||
public const char Separator = ':';
|
||||
|
||||
/// <summary>두 토큰으로 합성 키를 만든다. 예: Make("card", "101") → "card:101".</summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static string Make(string a, string b)
|
||||
{
|
||||
return a + Separator + b;
|
||||
}
|
||||
|
||||
/// <summary>세 토큰으로 합성 키를 만든다.</summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static string Make(string a, string b, string c)
|
||||
{
|
||||
return a + Separator + b + Separator + c;
|
||||
}
|
||||
|
||||
/// <summary>문자열과 정수 ID로 합성 키를 만든다.</summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static string Make(string category, int id)
|
||||
{
|
||||
return category + Separator + id.ToString(System.Globalization.CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
/// <summary>문자열과 두 정수 ID로 합성 키를 만든다. 예: 슬롯별 장비.</summary>
|
||||
public static string Make(string category, int idA, int idB)
|
||||
{
|
||||
return category + Separator + idA.ToString(System.Globalization.CultureInfo.InvariantCulture)
|
||||
+ Separator + idB.ToString(System.Globalization.CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
/// <summary>다수 토큰으로 합성 키를 만든다(원하는 수만큼 가변). 입력이 없으면 빈 문자열.</summary>
|
||||
public static string MakeParams(params string[] tokens)
|
||||
{
|
||||
if (tokens == null || tokens.Length == 0) return string.Empty;
|
||||
return string.Join(Separator.ToString(), tokens);
|
||||
}
|
||||
|
||||
/// <summary>키의 최상위(첫 번째 구분자 앞) 토큰을 반환. 구분자가 없으면 전체 반환.</summary>
|
||||
public static string SplitRoot(string key)
|
||||
{
|
||||
if (string.IsNullOrEmpty(key)) return string.Empty;
|
||||
int idx = key.IndexOf(Separator);
|
||||
return idx < 0 ? key : key.Substring(0, idx);
|
||||
}
|
||||
|
||||
/// <summary>키에 <paramref name="prefix"/> 가 붙어 있는지 확인한다(구분자 포함 체크).</summary>
|
||||
public static bool StartsWithCategory(string key, string prefix)
|
||||
{
|
||||
if (string.IsNullOrEmpty(key) || string.IsNullOrEmpty(prefix)) return false;
|
||||
if (!key.StartsWith(prefix)) return false;
|
||||
if (key.Length == prefix.Length) return true; // 완전 일치도 동일 카테고리로 간주
|
||||
return key[prefix.Length] == Separator;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,110 @@
|
|||
// ---------------------------------------------------------------------------
|
||||
// NerdNavis.Framework
|
||||
// MathEx.cs — 게임 빈출 수학 유틸리티 (Remap, Clamp, 부드러운 감쇠 등)
|
||||
// ---------------------------------------------------------------------------
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace NerdNavis.Core.Util
|
||||
{
|
||||
/// <summary>
|
||||
/// UnityEngine.Mathf 에 없는 게임 빈출 수학 연산을 모은 유틸리티.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>설계 문서 §5 Tier 1. UnityEngine 참조 없이 순수 연산만 제공하므로 서버·배치 컨텍스트에서도 재사용 가능(C11 코드 범용성).</para>
|
||||
/// </remarks>
|
||||
public static class MathEx
|
||||
{
|
||||
/// <summary><see cref="float"/> 기준 작은 오차(1e-6).</summary>
|
||||
public const float Epsilon = 1e-6f;
|
||||
|
||||
/// <summary>값을 <c>[0,1]</c>로 clamp한다.</summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static float Clamp01(float value)
|
||||
{
|
||||
if (value < 0f) return 0f;
|
||||
if (value > 1f) return 1f;
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>값을 <c>[min,max]</c>로 clamp한다.</summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int Clamp(int value, int min, int max)
|
||||
{
|
||||
if (value < min) return min;
|
||||
if (value > max) return max;
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary><see cref="float"/> 값을 <c>[min,max]</c>로 clamp한다.</summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static float Clamp(float value, float min, float max)
|
||||
{
|
||||
if (value < min) return min;
|
||||
if (value > max) return max;
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>입력 구간 <c>[inMin,inMax]</c> 의 값을 출력 구간 <c>[outMin,outMax]</c> 로 선형 리매핑한다.</summary>
|
||||
/// <remarks>입력 구간이 0이면 <paramref name="outMin"/> 을 반환한다(0-division 회피).</remarks>
|
||||
public static float Remap(float value, float inMin, float inMax, float outMin, float outMax)
|
||||
{
|
||||
float span = inMax - inMin;
|
||||
if (span > -Epsilon && span < Epsilon) return outMin;
|
||||
float t = (value - inMin) / span;
|
||||
return outMin + (outMax - outMin) * t;
|
||||
}
|
||||
|
||||
/// <summary>리매핑 후 출력 구간으로 clamp한다.</summary>
|
||||
public static float RemapClamped(float value, float inMin, float inMax, float outMin, float outMax)
|
||||
{
|
||||
float mapped = Remap(value, inMin, inMax, outMin, outMax);
|
||||
float lo = outMin < outMax ? outMin : outMax;
|
||||
float hi = outMin < outMax ? outMax : outMin;
|
||||
return Clamp(mapped, lo, hi);
|
||||
}
|
||||
|
||||
/// <summary>두 실수가 <paramref name="epsilon"/> 이내로 근사하면 <c>true</c>.</summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool Approximately(float a, float b, float epsilon = Epsilon)
|
||||
{
|
||||
float diff = a - b;
|
||||
return diff > -epsilon && diff < epsilon;
|
||||
}
|
||||
|
||||
/// <summary>프레임율 독립 부드러운 감쇠(Lerp with delta time).</summary>
|
||||
/// <param name="current">현재 값.</param>
|
||||
/// <param name="target">목표 값.</param>
|
||||
/// <param name="halfLife">목표까지 절반 접근에 걸리는 시간(초).</param>
|
||||
/// <param name="deltaTime">경과 시간(초).</param>
|
||||
/// <remarks>halfLife·deltaTime이 0 이하이면 즉시 <paramref name="target"/>을 반환한다.</remarks>
|
||||
public static float SmoothTowards(float current, float target, float halfLife, float deltaTime)
|
||||
{
|
||||
if (halfLife <= 0f || deltaTime <= 0f) return target;
|
||||
double k = Math.Pow(0.5, deltaTime / halfLife);
|
||||
return target + (float)((current - target) * k);
|
||||
}
|
||||
|
||||
/// <summary>두 정수의 최대공약수(GCD). 음수 입력은 절대값으로 처리.</summary>
|
||||
public static int Gcd(int a, int b)
|
||||
{
|
||||
if (a < 0) a = -a;
|
||||
if (b < 0) b = -b;
|
||||
while (b != 0)
|
||||
{
|
||||
int t = b;
|
||||
b = a % b;
|
||||
a = t;
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
/// <summary>오버플로 없이 <c>long</c>으로 계산한 최소공배수(LCM). 둘 다 0이면 0.</summary>
|
||||
public static long Lcm(int a, int b)
|
||||
{
|
||||
if (a == 0 || b == 0) return 0;
|
||||
int g = Gcd(a, b);
|
||||
return (long)(a / g) * b;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
// ---------------------------------------------------------------------------
|
||||
// NerdNavis.Framework
|
||||
// ValidationEx.cs — 인자·입력 검증 헬퍼 (throw 규약 통일)
|
||||
// ---------------------------------------------------------------------------
|
||||
using System;
|
||||
using System.Collections;
|
||||
|
||||
namespace NerdNavis.Core.Util
|
||||
{
|
||||
/// <summary>
|
||||
/// 메서드 인자·상태 사전 검증을 위한 가드 헬퍼.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>설계 문서 §5 Tier 1. 프레임워크 전반에서 <see cref="ArgumentNullException"/>·
|
||||
/// <see cref="ArgumentOutOfRangeException"/>·<see cref="InvalidOperationException"/> 의 throw 규칙을
|
||||
/// 통일하여 호출자가 예측 가능한 예외 경로를 가질 수 있게 한다(C11 코드 직관성).</para>
|
||||
/// <para>성공 경로(값이 유효한 경우)는 원본 값을 그대로 반환하므로 체이닝 가능:</para>
|
||||
/// <code>
|
||||
/// this._name = ValidationEx.NotNull(name, nameof(name));
|
||||
/// </code>
|
||||
/// </remarks>
|
||||
public static class ValidationEx
|
||||
{
|
||||
/// <summary>값이 <c>null</c>이면 <see cref="ArgumentNullException"/>을 던지고, 아니면 값을 반환한다.</summary>
|
||||
public static T NotNull<T>(T value, string paramName) where T : class
|
||||
{
|
||||
if (value == null) throw new ArgumentNullException(paramName);
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>문자열이 <c>null</c>이거나 빈 문자열이면 <see cref="ArgumentException"/> 을 던진다.</summary>
|
||||
public static string NotNullOrEmpty(string value, string paramName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
throw new ArgumentException("must not be null or empty", paramName);
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>문자열이 <c>null</c>·빈 문자열·공백만 있으면 <see cref="ArgumentException"/> 을 던진다.</summary>
|
||||
public static string NotNullOrWhiteSpace(string value, string paramName)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
throw new ArgumentException("must not be null, empty, or whitespace", paramName);
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>컬렉션이 <c>null</c>이거나 원소가 0개면 <see cref="ArgumentException"/>을 던진다.</summary>
|
||||
public static T NotNullOrEmpty<T>(T collection, string paramName) where T : class, ICollection
|
||||
{
|
||||
if (collection == null) throw new ArgumentNullException(paramName);
|
||||
if (collection.Count == 0) throw new ArgumentException("collection must not be empty", paramName);
|
||||
return collection;
|
||||
}
|
||||
|
||||
/// <summary>정수가 <c>[min,max]</c> 범위 밖이면 <see cref="ArgumentOutOfRangeException"/>을 던진다.</summary>
|
||||
public static int InRange(int value, int min, int max, string paramName)
|
||||
{
|
||||
if (value < min || value > max)
|
||||
throw new ArgumentOutOfRangeException(paramName, value, $"must be in [{min},{max}]");
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>실수가 <c>[min,max]</c> 범위 밖이면 <see cref="ArgumentOutOfRangeException"/>을 던진다.</summary>
|
||||
public static float InRange(float value, float min, float max, string paramName)
|
||||
{
|
||||
if (value < min || value > max)
|
||||
throw new ArgumentOutOfRangeException(paramName, value, $"must be in [{min},{max}]");
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>정수가 0보다 크지 않으면 <see cref="ArgumentOutOfRangeException"/>을 던진다.</summary>
|
||||
public static int Positive(int value, string paramName)
|
||||
{
|
||||
if (value <= 0)
|
||||
throw new ArgumentOutOfRangeException(paramName, value, "must be > 0");
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>정수가 음수이면 <see cref="ArgumentOutOfRangeException"/>을 던진다(0 허용).</summary>
|
||||
public static int NonNegative(int value, string paramName)
|
||||
{
|
||||
if (value < 0)
|
||||
throw new ArgumentOutOfRangeException(paramName, value, "must be >= 0");
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>조건이 <c>false</c>이면 <see cref="InvalidOperationException"/>을 던진다(상태 검증).</summary>
|
||||
public static void State(bool condition, string message)
|
||||
{
|
||||
if (!condition) throw new InvalidOperationException(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
// ---------------------------------------------------------------------------
|
||||
// NerdNavis.Framework.Tests
|
||||
// AttributeTests.cs — ReadOnly/ShowIf/ArrayTitle 속성 단위 테스트
|
||||
// ---------------------------------------------------------------------------
|
||||
using System;
|
||||
using NUnit.Framework;
|
||||
using NerdNavis.Core.Attribute;
|
||||
|
||||
namespace NerdNavis.Tests.Core.Attribute
|
||||
{
|
||||
public class AttributeTests
|
||||
{
|
||||
[Test]
|
||||
public void ReadOnlyAttribute_IsSealedAndFieldOnly()
|
||||
{
|
||||
var t = typeof(ReadOnlyAttribute);
|
||||
Assert.IsTrue(t.IsSealed);
|
||||
var usage = (AttributeUsageAttribute)Attribute.GetCustomAttribute(t, typeof(AttributeUsageAttribute));
|
||||
Assert.IsNotNull(usage);
|
||||
Assert.That(usage.ValidOn, Is.EqualTo(AttributeTargets.Field));
|
||||
Assert.IsFalse(usage.AllowMultiple);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ShowIfAttribute_TruthyConstructor_SetsMemberNameAndNullExpected()
|
||||
{
|
||||
var attr = new ShowIfAttribute("_flag");
|
||||
Assert.That(attr.MemberName, Is.EqualTo("_flag"));
|
||||
Assert.IsNull(attr.ExpectedValue);
|
||||
Assert.IsTrue(attr.HideInInspector);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ShowIfAttribute_ValueConstructor_SetsExpectedValue()
|
||||
{
|
||||
var attr = new ShowIfAttribute("_mode", 2);
|
||||
Assert.That(attr.MemberName, Is.EqualTo("_mode"));
|
||||
Assert.That(attr.ExpectedValue, Is.EqualTo(2));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ShowIfAttribute_HideInInspectorOverridable()
|
||||
{
|
||||
var attr = new ShowIfAttribute("_flag", hideInInspector: false);
|
||||
Assert.IsFalse(attr.HideInInspector);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ArrayTitleAttribute_DefaultPrefixIsEmpty()
|
||||
{
|
||||
var attr = new ArrayTitleAttribute("Name");
|
||||
Assert.That(attr.MemberName, Is.EqualTo("Name"));
|
||||
Assert.That(attr.Prefix, Is.EqualTo(string.Empty));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ArrayTitleAttribute_PrefixStored()
|
||||
{
|
||||
var attr = new ArrayTitleAttribute("Name", "Card");
|
||||
Assert.That(attr.Prefix, Is.EqualTo("Card"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ArrayTitleAttribute_NullPrefixCoercedToEmpty()
|
||||
{
|
||||
var attr = new ArrayTitleAttribute("Name", null);
|
||||
Assert.That(attr.Prefix, Is.EqualTo(string.Empty));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
// ---------------------------------------------------------------------------
|
||||
// NerdNavis.Framework.Tests
|
||||
// EnumExTests.cs — EnumEx 메타데이터·파싱 유틸 단위 테스트
|
||||
// ---------------------------------------------------------------------------
|
||||
using NUnit.Framework;
|
||||
using NerdNavis.Core.Util;
|
||||
|
||||
namespace NerdNavis.Tests.Core.Util
|
||||
{
|
||||
public class EnumExTests
|
||||
{
|
||||
private enum Color { Red = 1, Green = 2, Blue = 4 }
|
||||
|
||||
[Test]
|
||||
public void GetValues_ReturnsAllDefinedValues()
|
||||
{
|
||||
var values = EnumEx.GetValues<Color>();
|
||||
Assert.That(values.Length, Is.EqualTo(3));
|
||||
CollectionAssert.AreEquivalent(new[] { Color.Red, Color.Green, Color.Blue }, values);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetValues_ReturnsCachedInstance()
|
||||
{
|
||||
var first = EnumEx.GetValues<Color>();
|
||||
var second = EnumEx.GetValues<Color>();
|
||||
Assert.AreSame(first, second, "배열 재할당이 발생하면 hot-path 비용이 증가한다");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetNames_ReturnsDefinedNames()
|
||||
{
|
||||
var names = EnumEx.GetNames<Color>();
|
||||
CollectionAssert.AreEquivalent(new[] { "Red", "Green", "Blue" }, names);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Count_ReturnsDefinedValueCount()
|
||||
{
|
||||
Assert.That(EnumEx.Count<Color>(), Is.EqualTo(3));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void IsDefined_TrueForDefinedValue()
|
||||
{
|
||||
Assert.IsTrue(EnumEx.IsDefined(Color.Green));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void IsDefined_FalseForUndefinedIntCastValue()
|
||||
{
|
||||
Color invalid = (Color)999;
|
||||
Assert.IsFalse(EnumEx.IsDefined(invalid));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TryParse_CaseInsensitiveByDefault()
|
||||
{
|
||||
Assert.IsTrue(EnumEx.TryParse<Color>("green", out var result));
|
||||
Assert.That(result, Is.EqualTo(Color.Green));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TryParse_ReturnsFalseForNullOrEmpty()
|
||||
{
|
||||
Assert.IsFalse(EnumEx.TryParse<Color>("", out _));
|
||||
Assert.IsFalse(EnumEx.TryParse<Color>(null, out _));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TryParse_RespectsCaseSensitivity()
|
||||
{
|
||||
Assert.IsFalse(EnumEx.TryParse<Color>("green", ignoreCase: false, out _));
|
||||
Assert.IsTrue(EnumEx.TryParse<Color>("Green", ignoreCase: false, out var r));
|
||||
Assert.That(r, Is.EqualTo(Color.Green));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
// ---------------------------------------------------------------------------
|
||||
// NerdNavis.Framework.Tests
|
||||
// EnumToIntTests.cs — EnumToInt 박싱-프리 변환 단위 테스트
|
||||
// ---------------------------------------------------------------------------
|
||||
using NUnit.Framework;
|
||||
using NerdNavis.Core.Util;
|
||||
|
||||
namespace NerdNavis.Tests.Core.Util
|
||||
{
|
||||
public class EnumToIntTests
|
||||
{
|
||||
private enum Sample { A = 0, B = 1, C = 42, Neg = -5 }
|
||||
|
||||
[Test]
|
||||
public void Convert_ReturnsUnderlyingIntValue()
|
||||
{
|
||||
Assert.That(EnumToInt.Convert(Sample.A), Is.EqualTo(0));
|
||||
Assert.That(EnumToInt.Convert(Sample.B), Is.EqualTo(1));
|
||||
Assert.That(EnumToInt.Convert(Sample.C), Is.EqualTo(42));
|
||||
Assert.That(EnumToInt.Convert(Sample.Neg), Is.EqualTo(-5));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FromInt_ReturnsMatchingEnum()
|
||||
{
|
||||
Assert.That(EnumToInt.FromInt<Sample>(42), Is.EqualTo(Sample.C));
|
||||
Assert.That(EnumToInt.FromInt<Sample>(-5), Is.EqualTo(Sample.Neg));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Equals_ComparesEnumValuesWithoutBoxing()
|
||||
{
|
||||
Assert.IsTrue(EnumToInt.Equals(Sample.C, Sample.C));
|
||||
Assert.IsFalse(EnumToInt.Equals(Sample.A, Sample.B));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RoundTrip_PreservesValue()
|
||||
{
|
||||
Sample original = Sample.C;
|
||||
int id = EnumToInt.Convert(original);
|
||||
Sample restored = EnumToInt.FromInt<Sample>(id);
|
||||
Assert.That(restored, Is.EqualTo(original));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
// ---------------------------------------------------------------------------
|
||||
// NerdNavis.Framework.Tests
|
||||
// FormatExTests.cs — FormatEx 포맷 유틸 단위 테스트
|
||||
// ---------------------------------------------------------------------------
|
||||
using NUnit.Framework;
|
||||
using NerdNavis.Core.Util;
|
||||
|
||||
namespace NerdNavis.Tests.Core.Util
|
||||
{
|
||||
public class FormatExTests
|
||||
{
|
||||
[Test]
|
||||
public void AbbreviateNumber_BelowThousand_ReturnsPlain()
|
||||
{
|
||||
Assert.That(FormatEx.AbbreviateNumber(0), Is.EqualTo("0"));
|
||||
Assert.That(FormatEx.AbbreviateNumber(42), Is.EqualTo("42"));
|
||||
Assert.That(FormatEx.AbbreviateNumber(999), Is.EqualTo("999"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AbbreviateNumber_UsesKMBTiers()
|
||||
{
|
||||
Assert.That(FormatEx.AbbreviateNumber(12_345), Is.EqualTo("12.3K"));
|
||||
Assert.That(FormatEx.AbbreviateNumber(1_500_000), Is.EqualTo("1.5M"));
|
||||
Assert.That(FormatEx.AbbreviateNumber(2_000_000_000), Is.EqualTo("2.0B"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AbbreviateNumber_NegativeKeepsSign()
|
||||
{
|
||||
Assert.That(FormatEx.AbbreviateNumber(-1_500), Is.EqualTo("-1.5K"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AbbreviateNumber_ZeroDecimalsStripsFraction()
|
||||
{
|
||||
Assert.That(FormatEx.AbbreviateNumber(1_500, decimals: 0), Is.EqualTo("2K"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FormatTimeSpan_UnderHour_MinSecOnly()
|
||||
{
|
||||
Assert.That(FormatEx.FormatTimeSpan(65f), Is.EqualTo("01:05"));
|
||||
Assert.That(FormatEx.FormatTimeSpan(0f), Is.EqualTo("00:00"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FormatTimeSpan_OverHour_IncludesHour()
|
||||
{
|
||||
Assert.That(FormatEx.FormatTimeSpan(3670f), Is.EqualTo("01:01:10"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FormatTimeSpan_NegativeClampsToZero()
|
||||
{
|
||||
Assert.That(FormatEx.FormatTimeSpan(-5f), Is.EqualTo("00:00"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FormatPercent_TwoDecimalsByDefault()
|
||||
{
|
||||
Assert.That(FormatEx.FormatPercent(0.1234f), Is.EqualTo("12.34%"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WithThousands_InsertsCommas()
|
||||
{
|
||||
Assert.That(FormatEx.WithThousands(1_234_567), Is.EqualTo("1,234,567"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FormatBytes_ScalesTo1024()
|
||||
{
|
||||
Assert.That(FormatEx.FormatBytes(512), Is.EqualTo("512B"));
|
||||
Assert.That(FormatEx.FormatBytes(2048), Is.EqualTo("2.00KB"));
|
||||
Assert.That(FormatEx.FormatBytes(1_048_576), Is.EqualTo("1.00MB"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
// ---------------------------------------------------------------------------
|
||||
// NerdNavis.Framework.Tests
|
||||
// KeyMakerTests.cs — KeyMaker 합성 키 단위 테스트
|
||||
// ---------------------------------------------------------------------------
|
||||
using NUnit.Framework;
|
||||
using NerdNavis.Core.Util;
|
||||
|
||||
namespace NerdNavis.Tests.Core.Util
|
||||
{
|
||||
public class KeyMakerTests
|
||||
{
|
||||
[Test]
|
||||
public void Separator_IsColon()
|
||||
{
|
||||
Assert.That(KeyMaker.Separator, Is.EqualTo(':'));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Make_TwoStrings_JoinsWithSeparator()
|
||||
{
|
||||
Assert.That(KeyMaker.Make("card", "101"), Is.EqualTo("card:101"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Make_StringAndInt_UsesInvariantFormat()
|
||||
{
|
||||
Assert.That(KeyMaker.Make("card", 101), Is.EqualTo("card:101"));
|
||||
Assert.That(KeyMaker.Make("card", -5), Is.EqualTo("card:-5"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Make_ThreeTokens_JoinsAll()
|
||||
{
|
||||
Assert.That(KeyMaker.Make("a", "b", "c"), Is.EqualTo("a:b:c"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void MakeParams_VarArgsJoin()
|
||||
{
|
||||
Assert.That(KeyMaker.MakeParams("a", "b", "c", "d"), Is.EqualTo("a:b:c:d"));
|
||||
Assert.That(KeyMaker.MakeParams(), Is.EqualTo(string.Empty));
|
||||
Assert.That(KeyMaker.MakeParams(null), Is.EqualTo(string.Empty));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SplitRoot_ReturnsFirstToken()
|
||||
{
|
||||
Assert.That(KeyMaker.SplitRoot("card:101"), Is.EqualTo("card"));
|
||||
Assert.That(KeyMaker.SplitRoot("card:101:x"), Is.EqualTo("card"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SplitRoot_NoSeparator_ReturnsWhole()
|
||||
{
|
||||
Assert.That(KeyMaker.SplitRoot("card"), Is.EqualTo("card"));
|
||||
Assert.That(KeyMaker.SplitRoot(""), Is.EqualTo(""));
|
||||
Assert.That(KeyMaker.SplitRoot(null), Is.EqualTo(""));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void StartsWithCategory_RequiresSeparatorBoundary()
|
||||
{
|
||||
Assert.IsTrue(KeyMaker.StartsWithCategory("card:101", "card"));
|
||||
Assert.IsTrue(KeyMaker.StartsWithCategory("card", "card"));
|
||||
// prefix가 substring만 일치하는 경우는 false (card ≠ cards)
|
||||
Assert.IsFalse(KeyMaker.StartsWithCategory("cards:101", "card"));
|
||||
Assert.IsFalse(KeyMaker.StartsWithCategory("", "card"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
// ---------------------------------------------------------------------------
|
||||
// NerdNavis.Framework.Tests
|
||||
// MathExTests.cs — MathEx 수학 유틸 단위 테스트
|
||||
// ---------------------------------------------------------------------------
|
||||
using NUnit.Framework;
|
||||
using NerdNavis.Core.Util;
|
||||
|
||||
namespace NerdNavis.Tests.Core.Util
|
||||
{
|
||||
public class MathExTests
|
||||
{
|
||||
[Test]
|
||||
public void Clamp01_BoundsToZeroOne()
|
||||
{
|
||||
Assert.That(MathEx.Clamp01(-0.5f), Is.EqualTo(0f));
|
||||
Assert.That(MathEx.Clamp01(1.5f), Is.EqualTo(1f));
|
||||
Assert.That(MathEx.Clamp01(0.5f), Is.EqualTo(0.5f));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Clamp_IntAndFloatHonorBounds()
|
||||
{
|
||||
Assert.That(MathEx.Clamp(5, 0, 10), Is.EqualTo(5));
|
||||
Assert.That(MathEx.Clamp(-1, 0, 10), Is.EqualTo(0));
|
||||
Assert.That(MathEx.Clamp(11, 0, 10), Is.EqualTo(10));
|
||||
Assert.That(MathEx.Clamp(1.5f, 0f, 1f), Is.EqualTo(1f));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Remap_LinearMappingWorks()
|
||||
{
|
||||
// [0,100] -> [0,1]
|
||||
Assert.That(MathEx.Remap(50f, 0f, 100f, 0f, 1f), Is.EqualTo(0.5f).Within(MathEx.Epsilon));
|
||||
// [0,100] -> [-1,1]
|
||||
Assert.That(MathEx.Remap(0f, 0f, 100f, -1f, 1f), Is.EqualTo(-1f).Within(MathEx.Epsilon));
|
||||
Assert.That(MathEx.Remap(100f, 0f, 100f, -1f, 1f), Is.EqualTo(1f).Within(MathEx.Epsilon));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Remap_ZeroSpan_ReturnsOutMin()
|
||||
{
|
||||
// 입력 범위가 0이면 0-division 없이 outMin 반환
|
||||
Assert.That(MathEx.Remap(5f, 3f, 3f, -10f, 10f), Is.EqualTo(-10f));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RemapClamped_ClipsOutsideInputRange()
|
||||
{
|
||||
Assert.That(MathEx.RemapClamped(-50f, 0f, 100f, 0f, 1f), Is.EqualTo(0f));
|
||||
Assert.That(MathEx.RemapClamped(150f, 0f, 100f, 0f, 1f), Is.EqualTo(1f));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Approximately_WithinEpsilonReturnsTrue()
|
||||
{
|
||||
Assert.IsTrue(MathEx.Approximately(1.0f, 1.0f + 1e-7f));
|
||||
Assert.IsFalse(MathEx.Approximately(1.0f, 1.1f));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SmoothTowards_ReachesTargetEventually()
|
||||
{
|
||||
float current = 0f;
|
||||
for (int i = 0; i < 1000; i++)
|
||||
{
|
||||
current = MathEx.SmoothTowards(current, 10f, halfLife: 0.1f, deltaTime: 0.1f);
|
||||
}
|
||||
Assert.That(current, Is.EqualTo(10f).Within(0.01f));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SmoothTowards_ZeroOrNegativeHalfLife_SnapsToTarget()
|
||||
{
|
||||
Assert.That(MathEx.SmoothTowards(0f, 10f, halfLife: 0f, deltaTime: 0.1f), Is.EqualTo(10f));
|
||||
Assert.That(MathEx.SmoothTowards(0f, 10f, halfLife: -1f, deltaTime: 0.1f), Is.EqualTo(10f));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Gcd_BasicCases()
|
||||
{
|
||||
Assert.That(MathEx.Gcd(12, 18), Is.EqualTo(6));
|
||||
Assert.That(MathEx.Gcd(17, 13), Is.EqualTo(1));
|
||||
Assert.That(MathEx.Gcd(-12, 18), Is.EqualTo(6));
|
||||
Assert.That(MathEx.Gcd(0, 5), Is.EqualTo(5));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Lcm_BasicCases()
|
||||
{
|
||||
Assert.That(MathEx.Lcm(4, 6), Is.EqualTo(12));
|
||||
Assert.That(MathEx.Lcm(0, 5), Is.EqualTo(0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
// ---------------------------------------------------------------------------
|
||||
// NerdNavis.Framework.Tests
|
||||
// ValidationExTests.cs — ValidationEx 가드 단위 테스트
|
||||
// ---------------------------------------------------------------------------
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NUnit.Framework;
|
||||
using NerdNavis.Core.Util;
|
||||
|
||||
namespace NerdNavis.Tests.Core.Util
|
||||
{
|
||||
public class ValidationExTests
|
||||
{
|
||||
[Test]
|
||||
public void NotNull_NonNull_ReturnsValue()
|
||||
{
|
||||
var obj = new object();
|
||||
Assert.AreSame(obj, ValidationEx.NotNull(obj, "x"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void NotNull_Null_Throws()
|
||||
{
|
||||
Assert.Throws<ArgumentNullException>(() => ValidationEx.NotNull<object>(null, "x"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void NotNullOrEmpty_String_ThrowsForEmpty()
|
||||
{
|
||||
Assert.Throws<ArgumentException>(() => ValidationEx.NotNullOrEmpty("", "x"));
|
||||
Assert.Throws<ArgumentException>(() => ValidationEx.NotNullOrEmpty((string)null, "x"));
|
||||
Assert.That(ValidationEx.NotNullOrEmpty("abc", "x"), Is.EqualTo("abc"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void NotNullOrWhiteSpace_ThrowsForWhitespace()
|
||||
{
|
||||
Assert.Throws<ArgumentException>(() => ValidationEx.NotNullOrWhiteSpace(" ", "x"));
|
||||
Assert.That(ValidationEx.NotNullOrWhiteSpace("abc", "x"), Is.EqualTo("abc"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void NotNullOrEmpty_Collection_ThrowsForEmpty()
|
||||
{
|
||||
var empty = new List<int>();
|
||||
Assert.Throws<ArgumentException>(() => ValidationEx.NotNullOrEmpty(empty, "x"));
|
||||
|
||||
var nonEmpty = new List<int> { 1 };
|
||||
Assert.AreSame(nonEmpty, ValidationEx.NotNullOrEmpty(nonEmpty, "x"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void InRange_Int_BoundsChecked()
|
||||
{
|
||||
Assert.That(ValidationEx.InRange(5, 0, 10, "x"), Is.EqualTo(5));
|
||||
Assert.That(ValidationEx.InRange(0, 0, 10, "x"), Is.EqualTo(0));
|
||||
Assert.That(ValidationEx.InRange(10, 0, 10, "x"), Is.EqualTo(10));
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() => ValidationEx.InRange(-1, 0, 10, "x"));
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() => ValidationEx.InRange(11, 0, 10, "x"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Positive_ThrowsForZeroAndNegative()
|
||||
{
|
||||
Assert.That(ValidationEx.Positive(1, "x"), Is.EqualTo(1));
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() => ValidationEx.Positive(0, "x"));
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() => ValidationEx.Positive(-1, "x"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void NonNegative_AllowsZero()
|
||||
{
|
||||
Assert.That(ValidationEx.NonNegative(0, "x"), Is.EqualTo(0));
|
||||
Assert.That(ValidationEx.NonNegative(1, "x"), Is.EqualTo(1));
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() => ValidationEx.NonNegative(-1, "x"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void State_ThrowsInvalidOperationOnFalse()
|
||||
{
|
||||
Assert.Throws<InvalidOperationException>(() => ValidationEx.State(false, "bad"));
|
||||
Assert.DoesNotThrow(() => ValidationEx.State(true, "ok"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,166 @@
|
|||
# 11. UI 아키텍처 — 수상한 잡화점
|
||||
|
||||
> **작성일**: 2026-04-17
|
||||
> **작성자**: 개발팀장
|
||||
> **상태**: v1 (초판, Phase 0-B 연계 문서)
|
||||
> **대상**: `Assets/Script/UGUI/` 전수 (Ingame 19 + Lobby 19 + Common/Title/Util/Manager/BackKey 등)
|
||||
> **관련 문서**: `08_전투시스템_SOT_v1.md`, `09_카드시스템_아키텍처_v1.md`, `10_데이터로딩_구조_v1.md`
|
||||
|
||||
---
|
||||
|
||||
## 1. 목적 (P18 §결정의 배경)
|
||||
|
||||
수상한 잡화점 UI 계층의 **구조·주요 매니저·상호작용 패턴**을 전수 식별하여:
|
||||
1. 전투·카드·데이터 문서(08·09·10)에 이은 Phase 0-B 완결 (UI는 기획 밸런싱·시뮬레이션의 최종 표시 계층)
|
||||
2. 코어 프레임워크 Tier 1 `NerdNavis.UI.UGUI`·`NerdNavis.UI.Components`에 흡수할 **범용 패턴 vs 프로젝트 특수 로직** 경계 확정 (C11)
|
||||
3. UI 기획 연동·UX 검증 작업 시 영향 범위 식별에 필요한 **단일 SOT**
|
||||
|
||||
## 2. 전체 계층 구조
|
||||
|
||||
```
|
||||
UGUI/
|
||||
├── Manager/ UI 전역 매니저 계층 — 씬·팝업·로딩 브릿지
|
||||
├── BackKey/ Android 뒤로가기 통합 처리
|
||||
├── Common/ 씬 공용 (GameUI, ScenarioUI, ToastUI, ControlUI 등)
|
||||
├── Util/ SafeArea, UI 확장 컴포넌트
|
||||
├── Title/ 타이틀·로딩·공지·계정 진입 흐름
|
||||
├── Lobby/ 로비 씬 UI (19개 스크립트)
|
||||
│ ├── LobbyUIManager ← 로비 최상위 오케스트레이터
|
||||
│ ├── LobbyTopUI, MoneyCard
|
||||
│ ├── MainMenu/ 영웅·카드·장비·미션·상점 (Base + 5개 탭)
|
||||
│ ├── Attandance/ 출석 보상
|
||||
│ ├── CatTrade/ 상점(고양이 상인) — 재화·장비·인장·메인 미션 통합 (11종)
|
||||
│ ├── Explore/ 탐험 — 지도·스테이지·보상 (7종)
|
||||
│ ├── SeasonPass/ 시즌 패스 (2종)
|
||||
│ └── ETC/
|
||||
├── Ingame/ 전투·던전 씬 UI (19개 스크립트)
|
||||
│ ├── IngameUIManager ← 인게임 최상위 오케스트레이터 (153 LOC)
|
||||
│ ├── DungeonProcess ← 스테이지 진행 UI (67 LOC)
|
||||
│ ├── SelectCardUI ← 카드 선택 (290 LOC, 최대 UI 단일 모듈)
|
||||
│ ├── BattleCard, BattleSmallCard, DeckUI, DeckUI_Skills, DeckUI_My4Spec
|
||||
│ ├── GainCardList, IngameTopUI, SpecificityCard/List
|
||||
│ ├── MerchantBuyPopup, PCMainStatUI, IngameMainStatCard
|
||||
│ ├── Result/ DefeatUI, WinUI, StageClearRewards, ResultComon
|
||||
│ └── ETC/
|
||||
└── zTestUI/
|
||||
```
|
||||
|
||||
## 3. 최상위 매니저 3종 — 오케스트레이션 책임
|
||||
|
||||
### 3-1. `LobbyUIManager` (Lobby/LobbyUIManager.cs, 202 LOC)
|
||||
- 로비 씬 전체 UI 탭 전환·활성화 제어
|
||||
- 탭 구성: MainMenu(영웅/카드/장비/미션/상점) · Attandance · CatTrade · Explore · SeasonPass
|
||||
- 각 탭 자체 열림 상태·재진입 초기화 책임은 하위 UI에 위임하되, **탭 간 배타 활성은 본 매니저가 통제**
|
||||
|
||||
### 3-2. `IngameUIManager` (Ingame/IngameUIManager.cs, 153 LOC)
|
||||
- 인게임(전투) 씬 UI 전역 오케스트레이터
|
||||
- 주요 책임: `DungeonProcess` 연동, 전투 카드 스폰/회수, 결과 UI(Result/) 트리거
|
||||
- 08 전투시스템 SOT 문서의 FSM 상태 전환 이벤트를 구독하여 UI 표시 갱신
|
||||
|
||||
### 3-3. `Title_Mgr` (Manager/Title_Mgr.cs)
|
||||
- 타이틀 씬 진입 — 로딩·공지·계정 인증 흐름 제어
|
||||
- `AddrResourceMgr`·`DataCheckMgr`와 협력 (Addressable 로드·마스터 데이터 체크)
|
||||
|
||||
## 4. 공통 UI 컴포넌트 (Common + Util)
|
||||
|
||||
| 컴포넌트 | 위치 | 범용성 | 프레임워크 흡수 대상 |
|
||||
|---------|------|--------|--------------------|
|
||||
| `GameUI` | Common/GameUI.cs | ★ 매우 높음 (UIView 추상화 출발점) | ✅ `NerdNavis.UI.UGUI.UIView` |
|
||||
| `ScenarioUI` | Common/ScenarioUI.cs | △ 프로젝트 특수 (시나리오 시스템) | ❌ 프로젝트 유지 |
|
||||
| `ToastUI` | Common/ToastUI.cs | ★ 매우 높음 | ✅ `NerdNavis.UI.Components` |
|
||||
| `TouchBlockUI` | Common/TouchBlockUI.cs | ★ 매우 높음 | ✅ `NerdNavis.UI.Components` |
|
||||
| `ControlUI`/`ControlUILock` | Common/ | ○ UI 입력 잠금 패턴 | ✅ `NerdNavis.UI.Components` |
|
||||
| `MainStatCardBase` | Common/MainStatCardBase.cs | △ 프로젝트 카드 베이스 | ❌ 프로젝트 유지 |
|
||||
| `ItemSimpleCard` | Common/ItemSimpleCard.cs | △ 프로젝트 아이템 카드 | ❌ 프로젝트 유지 |
|
||||
| `SafeArea` | Util/ (추정) | ★ 매우 높음 | ✅ `NerdNavis.UI.Components.SafeArea` |
|
||||
| `UIAtlasMgr` | UGUI/Manager/uScrollViewMgr 계열 | ★ 높음 | ✅ `NerdNavis.UI.UGUI.AtlasManager` |
|
||||
| 무한 스크롤 (`uScrollViewMgr`·`uScrollViewArrMgr`) | UGUI/Manager/ | ★ 높음 | ✅ `NerdNavis.UI.UGUI.VirtualScroll` |
|
||||
|
||||
> 범용성 표기: ★=1순위 흡수 · ○=2순위 · △=프로젝트 특수 · ❌=범용 불가
|
||||
|
||||
## 5. 핵심 UI 모듈 — 카드 중심 설계
|
||||
|
||||
### 5-1. BattleCard / BattleSmallCard (Ingame)
|
||||
- 전투 중 실제 표시되는 카드 UI. 09 카드시스템 아키텍처의 런타임 표시 계층
|
||||
- 지속 인스턴스 풀링 대상 (프레임 당 다수 스폰/회수)
|
||||
|
||||
### 5-2. SelectCardUI (Ingame, 290 LOC — 최대 단일 UI 모듈)
|
||||
- 카드 선택(획득 후보 3장 중 1장 선택) UI
|
||||
- 제약 조건(C7 재미): 선택 시간·UX 피드백·희귀도 연출이 집약됨
|
||||
- **프레임워크 흡수 비대상** — 프로젝트 특수 선택 규칙 (배제 조건·카드 풀 계산 등)
|
||||
|
||||
### 5-3. DeckUI 계열 (DeckUI, DeckUI_Skills, DeckUI_My4Spec)
|
||||
- 데크·스킬·4스펙(고유 특성) 표시 UI
|
||||
- 슬롯 기반 그리드 배치 패턴 → VirtualScroll·GridPool 흡수 후보
|
||||
|
||||
### 5-4. DungeonProcess (Ingame, 67 LOC)
|
||||
- 스테이지 진행 UI — 맵 패턴·배틀·이벤트·보스 표시
|
||||
- 맵 패턴 규칙(P17·기획팀 스테이지 설계)의 UI 표현 계층
|
||||
|
||||
## 6. 로비 기능별 UI 클러스터
|
||||
|
||||
### 6-1. MainMenu — 영웅·카드·장비·미션·상점 (5탭)
|
||||
- `Base/`: 공통 베이스
|
||||
- `1_Hero`·`2_Card`·`3_Equipment`·`4_Mission`: 각 탭 세부 (숫자 접두어 = 표시 순서)
|
||||
- `MainMenu_Shop.cs`: 상점 탭(본 스크립트는 `5_Shop` 폴더가 아닌 루트에 존재 — 폴더 미정립)
|
||||
|
||||
### 6-2. CatTrade — 상점(고양이 상인) 통합 (11개 스크립트, 로비 내 최대 클러스터)
|
||||
- 재화 거래(`CatTradeUI_Goods`)
|
||||
- 장비 거래(`CatTradeUI_Equipment` + `CatTradeEquipmentBuyPopup` + `EquipmentTradeInvenCard`)
|
||||
- 메인 미션(`CatTradeUI_MainMission`)
|
||||
- 인장(`CatTradeUI_Seal` + `GetSealUI` + `SealRoulleteCard` + `SealSlotScroller`)
|
||||
- 재화 카드(`CatTradeGoodsCard`)
|
||||
|
||||
### 6-3. Explore — 탐험 (7개 스크립트)
|
||||
- 맵(`ExploreUI_Map`·`ExploreMapName`·`ExploreUI_Map_Cloud`(안개)): 맵 노드·구름 연출
|
||||
- 스테이지(`ExploreUI_StageSelect`·`ExploreStageCard`): 노드 선택·카드 표시
|
||||
- 보상(`ExploreRewardPopup`)
|
||||
|
||||
### 6-4. SeasonPass · Attandance
|
||||
- 시즌 패스(`SeasonPassUI`·`SeasonPassCard`): 트랙·레벨·보상 표시
|
||||
- 출석(`AttandanceUI`·`AttandanceCard`): 일일 보상 슬롯
|
||||
|
||||
## 7. BackKey 통합 처리
|
||||
|
||||
- `BackKey/` 디렉토리: Android `Escape` 키 통합 처리
|
||||
- 열린 UI 스택 관리 → 가장 위 UI부터 순차 닫기
|
||||
- **프레임워크 흡수 후보**: `NerdNavis.UI.UGUI.BackKeyDispatcher` (안드로이드 필수 패턴)
|
||||
|
||||
## 8. 프레임워크 Tier 1 흡수 계획 (범용성 ★ 우선)
|
||||
|
||||
차기 프로젝트 활용을 위한 **범용 UI 프레임워크** 구성 요소:
|
||||
|
||||
| 모듈 | 네임스페이스 | 수상한 잡화점 출처 |
|
||||
|------|------------|------------------|
|
||||
| UIView 추상 베이스 | `NerdNavis.UI.UGUI.UIView` | `Common/GameUI` |
|
||||
| SafeArea | `NerdNavis.UI.Components.SafeArea` | `UGUI/Util/SafeArea` |
|
||||
| Toast | `NerdNavis.UI.Components.Toast` | `Common/ToastUI` |
|
||||
| 입력 차단 | `NerdNavis.UI.Components.InputBlocker` | `Common/TouchBlockUI`·`ControlUI` |
|
||||
| 아틀라스 매니저 | `NerdNavis.UI.UGUI.AtlasManager` | `UGUI/Manager/UIAtlasMgr` |
|
||||
| 무한 스크롤 | `NerdNavis.UI.UGUI.VirtualScroll` | `UGUI/Manager/uScrollView*Mgr` |
|
||||
| BackKey 디스패처 | `NerdNavis.UI.UGUI.BackKeyDispatcher` | `BackKey/` |
|
||||
|
||||
## 9. 기획 연동 포인트 (UX 검증 시 영향 범위)
|
||||
|
||||
| 기획 변경 범주 | 영향 UI 모듈 |
|
||||
|---------------|------------|
|
||||
| 카드 조건·배타(P17) | `SelectCardUI`, `SpecificityCard/List` |
|
||||
| 스테이지 맵 패턴 | `DungeonProcess`, `ExploreUI_Map`·`ExploreUI_StageSelect` |
|
||||
| 보상 수치 | `ExploreRewardPopup`, `StageClearRewards`, `Result/WinUI` |
|
||||
| 상점 재화 밸런스 | `CatTradeUI_*`, `MerchantBuyPopup`, `MoneyCard` |
|
||||
| 시즌 패스 보상 트랙 | `SeasonPassUI`, `SeasonPassCard` |
|
||||
| 출석 보상 | `AttandanceUI`, `AttandanceCard` |
|
||||
|
||||
## 10. 검증 방법 (P18 §검증)
|
||||
|
||||
1. 각 행의 파일 경로는 `Assets/Script/UGUI/{클러스터}/{파일명}.cs`에서 실존 확인 가능 (본 문서 작성 시 `ls`로 전수 확인 완료)
|
||||
2. LOC 표기는 `wc -l` 결과 기준
|
||||
3. 범용성 분류(★/○/△/❌)는 의존성 스캔(Unity 타입·게임 특수 enum 참조 여부)로 재검증 가능
|
||||
|
||||
## 11. 기각안 (P24 §기각안)
|
||||
|
||||
- **기각안 A: UIToolkit 병행 매핑 문서화** — 기획 방향이 UGUI 단일이므로 UIToolkit 매핑은 차기 프로젝트 R&D로 이관 (본 문서는 UGUI 전수 한정)
|
||||
- **기각안 B: 각 스크립트 public API 메서드 전수 목록화** — 토큰 비용 과다 + 기획·검증 영향도 분류 목적에 불필요. 구조 수준 분류로 대체
|
||||
|
||||
## 부록 A. 변경 이력
|
||||
- **v1 (2026-04-17)**: 초판. 개발팀장 Phase 0-B 완결 작업(B-4)으로 작성. Assets 전수 `ls` + 주요 파일 LOC 실측 기반.
|
||||
|
|
@ -0,0 +1,204 @@
|
|||
# 12. 메타시스템 — 수상한 잡화점
|
||||
|
||||
> **작성일**: 2026-04-17
|
||||
> **작성자**: 개발팀장
|
||||
> **상태**: v1 (초판, Phase 0-B 연계 문서)
|
||||
> **대상**: 세이브/로드 · 진행도 · 상점 · 성장(장비·각성·인장) · 시즌패스 · 탐험 · 출석
|
||||
> **관련 문서**: `05_서버연동_현황_v1.md`, `09_카드시스템_아키텍처_v1.md`, `10_데이터로딩_구조_v1.md`, `11_UI아키텍처_v1.md`
|
||||
|
||||
---
|
||||
|
||||
## 1. 목적 (P18 §결정의 배경)
|
||||
|
||||
수상한 잡화점의 **비전투(메타) 시스템** 전체 계층을 식별하여:
|
||||
1. 전투(08) · 카드(09) · 데이터(10) · UI(11)에 이은 Phase 0-B 최종 완결
|
||||
2. 서버 역할 문서(05·PD 지시 #30·#31)와의 **메타 시스템 측 대응표** 제공
|
||||
3. 차기 프로젝트 프레임워크 `NerdNavis.Save`·`NerdNavis.Economy` Tier 2 설계에 필요한 **실증 패턴** 공급
|
||||
4. 기획 밸런싱·유저 경험 변경 시 **영향 범위 식별 SOT**
|
||||
|
||||
## 2. 메타시스템 전체 맵
|
||||
|
||||
```
|
||||
메타시스템 = "전투 외 유저 진행·성장·경제 시스템"
|
||||
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 클라이언트 로컬 상태 (Assets/Script/Info/) │
|
||||
│ ├── ActorInfo — 캐릭터·영웅 상태 │
|
||||
│ ├── InGameInfo — 인게임 진행 상태 │
|
||||
│ ├── InappInfo — 인앱 결제 영수증·트랜잭션 │
|
||||
│ ├── OptionInfo — 유저 옵션(사운드·언어·그래픽) │
|
||||
│ ├── TitleInfo — 타이틀·공지 │
|
||||
│ ├── ADInfo — 광고 상태 │
|
||||
│ ├── SoundInfo, WebViewInfo, UtilInfo, Popup, NetWait │
|
||||
│ └── (*.Info 단일 데이터 클래스 + 매니저 스타일) │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
↕ (마스터 테이블 조회, 10 문서)
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 서버 연동 (Assets/Script/Server/) │
|
||||
│ ├── ServerClass.cs — 서버 응답/요청 DTO │
|
||||
│ ├── ServerInfo.cs — 서버 상태·세션 관리 │
|
||||
│ └── (PlayFab 전제, 05 문서 및 서버 지시서 v1.1/v1.2 참조) │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
↕ (UI 표시, 11 문서)
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ UI 계층 (Assets/Script/UGUI/Lobby/) │
|
||||
│ └── 본 문서 §4 각 시스템별 UI 매핑 │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## 3. 세이브/로드 구조
|
||||
|
||||
### 3-1. 현 구조 (PlayFab 중심)
|
||||
- **원본**: PlayFab(`UserData`/`TitleData`/`PlayerStatistics`) — 서버 지시서 v1.1 §4·§5 기준
|
||||
- **로컬 캐시**: `Info/*.Info` 각 클래스 내부에 필드 보존 + 필요 시 `PlayerPrefs` 부분 저장(옵션·로컬 전용)
|
||||
- **인코딩**: JSON(PlayFab 기본 직렬화) + `My/CryptoUtil.cs` 적용분은 일부 (세이브 전반 체계화 안 됨)
|
||||
|
||||
### 3-2. 약점·리스크
|
||||
1. **SOT 분리 모호** — `*.Info` 클래스가 "런타임 상태 + 저장 대상" 이중 역할
|
||||
2. **세이브 스키마 버전 관리 부재** — 필드 추가/제거 시 마이그레이션 전략 없음
|
||||
3. **로컬/서버 동기화 타이밍이 호출부에 산재** — 조회 시마다 서버 호출 or 캐시 사용 판단 분산
|
||||
|
||||
### 3-3. Tier 2 `NerdNavis.Save` 설계 반영 포인트
|
||||
- `ISaveProvider` 인터페이스 (PlayerPrefs / JSON / 암호화 / 클라우드)
|
||||
- 버전 마이그레이션 훅(`IMigration`) 신설
|
||||
- 세이브 대상 POCO와 런타임 상태 클래스 분리 (DTO 패턴)
|
||||
|
||||
## 4. 진행도 · 성장 · 경제 시스템 (영역별)
|
||||
|
||||
### 4-1. 영웅·카드·장비·각성·인장 (성장)
|
||||
|
||||
| 시스템 | 클라 코드 위치 | UI 클러스터 | 마스터 테이블 |
|
||||
|--------|--------------|-----------|-------------|
|
||||
| 영웅 (Hero) | `Info/ActorInfo.cs` + 스킬·스펙 서브 | `Lobby/MainMenu/1_Hero/` | Hero, Skill, HeroSkill |
|
||||
| 카드 (Card) | 09 문서 런타임 모듈 + `Info/` | `Lobby/MainMenu/2_Card/` | Card, CardEffect |
|
||||
| 장비 (Equipment) | `Info/`·CatTrade 관련 | `Lobby/MainMenu/3_Equipment/`·`Lobby/CatTrade/CatTradeUI_Equipment.cs` | Equipment, EquipmentOption |
|
||||
| 미션 (Mission) | `Info/`·`CatTradeUI_MainMission` | `Lobby/MainMenu/4_Mission/`·`Lobby/CatTrade/CatTradeUI_MainMission.cs` | Mission, MissionReward |
|
||||
| 인장 (Seal) | CatTrade Seal 서브 | `Lobby/CatTrade/CatTradeUI_Seal.cs`·`GetSealUI.cs`·`SealRoulleteCard.cs`·`SealSlotScroller.cs` | Seal, SealOption |
|
||||
|
||||
### 4-2. 상점 (CatTrade — 고양이 상인)
|
||||
- **구조**: 단일 상인이 5개 카테고리(재화·장비·인장·메인 미션 + 공통 Goods) 통합 제공
|
||||
- **UI 클러스터**: `Lobby/CatTrade/` 11개 스크립트 (로비 최대 클러스터, 11 문서 §6-2 참조)
|
||||
- **특수 로직**:
|
||||
- 인장 룰렛(`SealRoulleteCard`·`SealSlotScroller`): 슬롯머신 연출 — 확률 기반 획득
|
||||
- 장비 구매 팝업(`CatTradeEquipmentBuyPopup`) + 기존 보유 장비 비교(`EquipmentTradeInvenCard`)
|
||||
|
||||
### 4-3. 탐험 (Explore — 스테이지 선택)
|
||||
- **구조**: 맵 → 스테이지 노드 선택 → 전투 진입 or 이벤트
|
||||
- **UI 클러스터**: `Lobby/Explore/` 7개 스크립트 (11 문서 §6-3)
|
||||
- **저장 대상**:
|
||||
- 현재 탐험 지도 ID·해제된 노드
|
||||
- 각 노드 클리어 여부·최고 스코어(3성 조건 달성)
|
||||
- 탐험 보상 수령 이력
|
||||
- **P17 연계**: 스테이지별 ★ 조건 배치는 마스터 테이블(기획팀 `Stage`·`StarCondition`)에서 읽어 `ExploreUI_StageSelect`에 표시
|
||||
|
||||
### 4-4. 시즌 패스
|
||||
- **UI**: `Lobby/SeasonPass/SeasonPassUI.cs`·`SeasonPassCard.cs`
|
||||
- **저장 대상**: 현재 시즌 ID·시즌 진행도 경험치·무료/유료 트랙별 수령 레벨
|
||||
- **서버 동기화 필요**: 시즌 교체·유료 트랙 결제 영수증 (PlayFab)
|
||||
|
||||
### 4-5. 출석
|
||||
- **UI**: `Lobby/Attandance/AttandanceUI.cs`·`AttandanceCard.cs`
|
||||
- **저장 대상**: 현재 월/회차·연속 출석일·수령 이력
|
||||
- **서버 시간 의존**: 날짜 조작 방지 위해 서버 시간 기준 (서버 지시서 §3 참고)
|
||||
|
||||
## 5. 재화·경제 시스템
|
||||
|
||||
### 5-1. 재화 종류
|
||||
- 수상한 잡화점은 **복수 재화 체계** (골드·다이아·이벤트 화폐·카드 조각 등)
|
||||
- 현 구조: `Info/*.Info` 내 변수 + 마스터 테이블 `Goods`·`Currency` 참조 추정
|
||||
- 표시 UI: `Lobby/LobbyTopUI.cs` + `Lobby/MoneyCard.cs` + 인게임 `IngameTopUI.cs`
|
||||
|
||||
### 5-2. 획득 경로
|
||||
- 전투 클리어(`Ingame/Result/StageClearRewards`)
|
||||
- 탐험 보상(`Lobby/Explore/ExploreRewardPopup`)
|
||||
- 상점 구매(`Lobby/CatTrade/MerchantBuyPopup` 포함)
|
||||
- 인앱 결제(`Info/InappInfo.cs` + PlayFab 영수증 검증)
|
||||
- 광고 시청(`Info/ADInfo.cs`)
|
||||
- 출석·시즌 패스 보상
|
||||
|
||||
### 5-3. 차기 프로젝트 흡수 (`NerdNavis.Economy`)
|
||||
- `Goods` 범용 재화 모델 (타입·수량·최대치·오버플로 정책) — 01 설계안 §6-0 기정립
|
||||
- 인벤토리·획득 이벤트 훅(`EventBus` 연동)
|
||||
- **본 프로젝트 특수 로직(재화 종류·상점 UX)은 흡수 불가** — 프레임워크는 "재화 모델 컨테이너"만 제공
|
||||
|
||||
## 6. 서버 연동 상태 (05 문서 연계)
|
||||
|
||||
### 6-1. 현 서버 범위 (PlayFab)
|
||||
- 세션/인증 → `ServerInfo.cs`
|
||||
- 유저 데이터 로드/저장 → `ServerClass.cs` DTO 매핑
|
||||
- 스테이지 결과 기록 → `Save_StageResult` API (서버 지시서 §6 샘플)
|
||||
- 인앱 영수증 검증 → PlayFab 영수증 검증
|
||||
|
||||
### 6-2. 서버 역할 경계 (서버 지시서 §5·§6)
|
||||
- **클라 100% 책임**: 어뷰징 판정 (`is_abuse_flag`만 서버 전송, 경계값 보관·검증 안 함)
|
||||
- **서버 100% 책임**: 시간 기반 판정(출석·시즌 만료) · 영수증 검증 · 로그 집계
|
||||
- **양쪽 책임**: 세이브 동기화 (클라 전송 + 서버 검증 최소 필터)
|
||||
|
||||
## 7. 메타시스템 의존성 그래프 (핵심 흐름)
|
||||
|
||||
```
|
||||
로그인 → Title_Mgr
|
||||
↓ (서버 인증 → PlayFab)
|
||||
ServerInfo.Login → *.Info 다중 로드
|
||||
↓ (마스터 테이블 로드, 10 문서)
|
||||
DataCheckMgr·AddrResourceMgr
|
||||
↓
|
||||
LobbyUIManager.Initialize
|
||||
├→ LobbyTopUI (재화 표시)
|
||||
├→ MainMenu (영웅·카드·장비·미션 탭)
|
||||
├→ CatTrade (상점 진입)
|
||||
├→ Explore (탐험 진입)
|
||||
│ ↓ (스테이지 선택)
|
||||
│ IngameUIManager → 08 전투 FSM
|
||||
│ ↓ (전투 종료)
|
||||
│ StageClearRewards → *.Info 갱신 → ServerClass 전송
|
||||
│ ↓
|
||||
│ LobbyUIManager 복귀
|
||||
├→ SeasonPass / Attandance (보상 수령)
|
||||
└→ Option/WebView 등
|
||||
```
|
||||
|
||||
## 8. 기획 연동 포인트 (밸런싱·UX 변경 영향 범위)
|
||||
|
||||
| 기획 변경 범주 | 영향 메타 모듈 |
|
||||
|---------------|--------------|
|
||||
| 재화 밸런스 (획득량·환율) | `Info/*.Info` 재화 필드 + `LobbyTopUI`·`MoneyCard`·`IngameTopUI` + 상점 UI 전체 |
|
||||
| 성장 곡선 (장비 레벨·인장 등급) | `MainMenu_Equipment`·`CatTrade Seal 계열` + 마스터 테이블 `Equipment`·`Seal` |
|
||||
| 시즌 패스 트랙 구성 | `SeasonPassUI`·`SeasonPassCard` + 서버 시즌 ID 갱신 |
|
||||
| 출석 보상 트랙 | `AttandanceUI`·`AttandanceCard` + 서버 시간 의존 |
|
||||
| 탐험 맵 구조 (노드·분기) | `ExploreUI_Map`·`ExploreStageCard` + 마스터 `ExploreMap`·`Stage` |
|
||||
| 상점 가격·확률 (룰렛 등) | `CatTrade*` 전체 + 마스터 `Shop`·`Seal` |
|
||||
|
||||
## 9. 프레임워크 흡수 계획 (Tier 2 설계 반영)
|
||||
|
||||
| 프레임워크 모듈 | 수상한 잡화점 출처 | 흡수 수준 |
|
||||
|--------------|------------------|---------|
|
||||
| `NerdNavis.Save.ISaveProvider` | 현 `*.Info` + PlayFab 호출 패턴 | 🟡 신규 인터페이스 (기존 구조는 참고만) |
|
||||
| `NerdNavis.Save.IMigration` | 없음 (부재 자체가 교훈) | 🔴 신규 설계 |
|
||||
| `NerdNavis.Economy.Goods` | `MoneyCard`·재화 Info 필드 | 🟢 구조 계승, 네이밍만 변경 |
|
||||
| `NerdNavis.Economy.Inventory` | 장비·인장·카드 목록 패턴 | 🟡 범용 컨테이너 추출 |
|
||||
| `NerdNavis.Network.IReceiptVerifier` | `Info/InappInfo.cs` + PlayFab 영수증 | 🟡 Tier 3 편입 (서버팀 합류 시점) |
|
||||
|
||||
## 10. 현 프로젝트에서의 차기 개선 안건 (차기 프로젝트 참고 자료)
|
||||
|
||||
> 헌법 제1원칙 목표 2 원칙 B: 수상한 잡화점 인사이트를 차기 프로젝트 참고 자료로.
|
||||
|
||||
1. `*.Info` 단일 클래스에 "런타임 상태 + 직렬화 대상 + 서버 통신 DTO"를 모두 담는 구조는 차기 프로젝트에서 **반드시 분리** (DTO + 상태 + 리포지토리 3계층)
|
||||
2. 세이브 버전 관리 부재 → 차기 프로젝트 `IMigration` 훅 필수
|
||||
3. 서버/클라 동기화 타이밍이 호출부에 분산 → 차기 프로젝트는 **중앙 동기화 매니저** 도입
|
||||
4. 재화 종류가 하드코딩(필드 단위) → 차기 프로젝트는 `Goods` 범용 모델 + 타입 enum 기반 Dictionary 저장
|
||||
|
||||
## 11. 검증 방법 (P18 §검증)
|
||||
|
||||
1. 각 Info/Server/UGUI 파일 경로는 실제 Unity 프로젝트에서 실존 확인 완료 (작성 시 `ls` + `find` 실측)
|
||||
2. `find` 기반으로 `PlayFab`·`SaveData`·`UserData` 키워드를 검색한 결과는 `ServerClass.cs`·`ServerInfo.cs`·`Info/` 6개 파일로 집중됨 — 본 문서 §2 구조와 일치
|
||||
3. 마스터 테이블 이름은 10 문서(데이터 로딩) 및 서버 지시서 §6 샘플을 교차 참조하여 추정 (기획팀 테이블 정의 재확인 필요 표시)
|
||||
|
||||
## 12. 기각안 (P24 §기각안)
|
||||
|
||||
- **기각안 A: 각 Info 클래스의 필드별 세이브 대상 분류표 작성** — 토큰 비용 + 프레임워크 흡수 대비 정보량 과다. 구조 수준 분류로 대체. 필요 시 Phase 0-C에서 세부 감사
|
||||
- **기각안 B: 서버 API 전수 매핑** — 서버 지시서 v1.1/v1.2가 별도 SOT로 존재. 본 문서는 클라 메타 구조에 집중
|
||||
- **기각안 C: 메타시스템 보안 취약점 감사** — `05_서버연동_현황_v1.md` Critical 3건이 이미 추적 중(#2 보류). 본 문서는 중복 분석 안 함
|
||||
|
||||
## 부록 A. 변경 이력
|
||||
- **v1 (2026-04-17)**: 초판. 개발팀장 Phase 0-B 최종 완결 작업(B-5)으로 작성. Unity 프로젝트 `Assets/Script/` 전수 `ls` + 키워드 `find` 실측 기반. 서버 지시서 v1.1/v1.2·05 문서·09·10·11 문서 교차 참조.
|
||||
|
|
@ -162,3 +162,14 @@ PC 스탯 → 몬스터 스탯 → 성장곡선 → 장비 → 인장 → 카드
|
|||
2. **Phase 0의 앵커 기준(Stage1, PC6001, 장비/각성/인장 없음, G1만)**이 적절한가요?
|
||||
3. **Phase 3의 기여도 목표(카드 +80~120%, 세트 장비 +60~80%, 각성 +40~60%…)**의 방향이 맞나요?**
|
||||
4. **실행 로드맵의 주차별 진행이 소프트 론칭 일정에 현실적인가요?**
|
||||
|
||||
---
|
||||
|
||||
## 변경 이력 (P16 산출물 추적성)
|
||||
|
||||
> **표준 포맷 (2026-04-17 도입)**: 밸런싱 관련 문서 4종 공통 적용. 본 문서의 전략·Phase 정의·기여도 목표가 변경될 때마다 아래 테이블에 1행 append.
|
||||
> **필드 설명**: 변경 필드 = 어떤 Phase·기여도 축·로드맵인가 / 이전값→이후값 = 구체 정의 / 재미 근거 = C7 원칙에 따른 재미 강화 축 / 관련 PD 지시# = `공유/PD_지시_트래킹/기획팀_PD_지시_로그.md`의 해당 #
|
||||
|
||||
| 일시 | 변경자 | 변경 필드 | 이전값→이후값 | 재미 근거 | 관련 PD 지시# |
|
||||
|------|--------|---------|-------------|----------|-------------|
|
||||
| 2026-04-17 | 기획팀장 | 변경 이력 테이블 섹션 신설 | - → 기존 상태 확정 | 산출물 추적성 확보 (P16), 전략 수정 시 결정 경로 추적 가능 | 본 작업 (팀장 재량 진행 승인) |
|
||||
|
|
|
|||
|
|
@ -372,3 +372,14 @@ Headless C# 시뮬 추출 후 각 조합을 실측:
|
|||
|
||||
*작성 완료: 2026-04-14*
|
||||
*상태: PD님 A안 "빌드 전수 점검" 이행 / Phase 3 재개 후 이슈 1·3 재논의 시 입력 자료*
|
||||
|
||||
---
|
||||
|
||||
## 변경 이력 (P16 산출물 추적성)
|
||||
|
||||
> **표준 포맷 (2026-04-17 도입)**: 밸런싱 관련 문서 4종 공통 적용. 본 문서의 충돌·배타 조합 판정·빌드 축이 변경될 때마다 아래 테이블에 1행 append.
|
||||
> **필드 설명**: 변경 필드 = 어떤 조합·빌드 축·충돌 판정인가 / 이전값→이후값 = 구체 판정 변화 / 재미 근거 = C7 원칙에 따른 재미 강화 축 (빌드 다양성 등) / 관련 PD 지시# = `공유/PD_지시_트래킹/기획팀_PD_지시_로그.md`의 해당 #
|
||||
|
||||
| 일시 | 변경자 | 변경 필드 | 이전값→이후값 | 재미 근거 | 관련 PD 지시# |
|
||||
|------|--------|---------|-------------|----------|-------------|
|
||||
| 2026-04-17 | 기획팀장 | 변경 이력 테이블 섹션 신설 | - → 기존 상태 확정 | 산출물 추적성 확보 (P16), 배타 조합(P17) 변경 시 근거 추적 가능 | 본 작업 (팀장 재량 진행 승인) |
|
||||
|
|
|
|||
|
|
@ -307,3 +307,14 @@ Stage: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
|||
|
||||
*분석 완료: 2026-04-13*
|
||||
*분석자: Claude (데이터 자동 추출 및 계산)*
|
||||
|
||||
---
|
||||
|
||||
## 변경 이력 (P16 산출물 추적성)
|
||||
|
||||
> **표준 포맷 (2026-04-17 도입)**: 밸런싱 관련 문서 4종 공통 적용. 본 문서의 수치·구간·난이도 평가가 변경될 때마다 아래 테이블에 1행 append.
|
||||
> **필드 설명**: 변경 필드 = 어떤 구간·스테이지·수치인가 / 이전값→이후값 = 구체 수치 / 재미 근거 = C7 원칙에 따른 재미 강화 축 / 관련 PD 지시# = `공유/PD_지시_트래킹/기획팀_PD_지시_로그.md`의 해당 #
|
||||
|
||||
| 일시 | 변경자 | 변경 필드 | 이전값→이후값 | 재미 근거 | 관련 PD 지시# |
|
||||
|------|--------|---------|-------------|----------|-------------|
|
||||
| 2026-04-17 | 기획팀장 | 변경 이력 테이블 섹션 신설 | - → 기존 상태 확정 | 산출물 추적성 확보 (P16), 차기 밸런스 변경 시 근거 비교 가능 | 본 작업 (팀장 재량 진행 승인) |
|
||||
|
|
|
|||
|
|
@ -581,3 +581,14 @@ D:/NerdNavis/FilGoodBandits/DeckBuilding/Assets/ResWork/Table/Export/
|
|||
ItemList.json - 아이템/재화 (225)
|
||||
RewardRandomBag.json - 보상 풀 (1342)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 변경 이력 (P16 산출물 추적성)
|
||||
|
||||
> **표준 포맷 (2026-04-17 도입)**: 밸런싱 관련 문서 4종 공통 적용. 본 문서의 테이블 감사 결과·Export 목록·스탯 범위 판정이 변경될 때마다 아래 테이블에 1행 append.
|
||||
> **필드 설명**: 변경 필드 = 어떤 테이블·감사 항목인가 / 이전값→이후값 = 구체 감사 결과 변화 / 재미 근거 = C7 원칙에 따른 재미 강화 축 / 관련 PD 지시# = `공유/PD_지시_트래킹/기획팀_PD_지시_로그.md`의 해당 #
|
||||
|
||||
| 일시 | 변경자 | 변경 필드 | 이전값→이후값 | 재미 근거 | 관련 PD 지시# |
|
||||
|------|--------|---------|-------------|----------|-------------|
|
||||
| 2026-04-17 | 기획팀장 | 변경 이력 테이블 섹션 신설 | - → 기존 상태 확정 | 산출물 추적성 확보 (P16), 테이블 구조 변경 추적 가능 | 본 작업 (팀장 재량 진행 승인) |
|
||||
|
|
|
|||
Loading…
Reference in New Issue