프로젝트 소스 공개
This commit is contained in:
@@ -0,0 +1,482 @@
|
||||
# ProjectQuint 로컬 프로필·성장 시스템 설계
|
||||
|
||||
상태: 핵심 저장·집계·프로필 탐색 구현 완료
|
||||
기준일: 2026-07-22
|
||||
적용 범위: 서버 없는 로컬 프로필, 4K/5K/6K/7K 및 scratch, 외부 BMS 팩·난이도표
|
||||
|
||||
## 1. 목표
|
||||
|
||||
ProjectQuint의 성장은 하나의 숫자로 모든 것을 표현하지 않는다. 플레이 누적, 실제 실력,
|
||||
단위인정, 곡 수집과 꾸미기 보상을 독립된 축으로 관리한다.
|
||||
|
||||
1. `Profile Level`: 얼마나 꾸준히 플레이했는지를 보여주는 활동 지표
|
||||
2. `Rating`: 난이도 상수가 있는 채보의 최고 플레이 30개로 계산하는 기록 지표
|
||||
3. `Course Certification`: 단위인정 코스를 통과했다는 범주형 증명
|
||||
4. `Chart Mastery / Collection`: 곡별 클리어·하드클리어·풀콤보·이론치 수집
|
||||
5. `Achievements / Cosmetics`: 장기 목표와 프로필 꾸미기 보상
|
||||
|
||||
Profile Level은 판정, 게이지, 점수에 어떠한 이점도 주지 않는다. 사용자가 직접 가져온 BMS
|
||||
곡이나 채보도 레벨로 잠그지 않는다.
|
||||
|
||||
## 2. 경쟁작에서 확인한 성장 문법
|
||||
|
||||
### osu!
|
||||
|
||||
- 프로필 Level은 누적 total score에 기반한 활동량이고 플레이 능력에 영향을 주지 않는다.
|
||||
- 실력은 별도의 performance points(pp)로 표현하며 게임 모드마다 분리한다.
|
||||
- pp는 높은 성과부터 정렬한 뒤 `0.95^(순위-1)`로 기여도를 줄여, 낮은 성과를 무한히
|
||||
쌓는 것보다 어려운 곡의 좋은 기록을 갱신하도록 유도한다.
|
||||
|
||||
ProjectQuint에 적용할 점: 활동 레벨과 실력 레이팅의 완전한 분리, 키 모드 분리, 상위 성과
|
||||
가중 집계.
|
||||
|
||||
### beatmania IIDX
|
||||
|
||||
- 곡별 최고 EX SCORE, clear lamp, 단위인정 기록을 서로 다른 성과로 보존한다.
|
||||
- DJ RANK 계열 지표는 카테고리별로 높은 DJ POINT 기록을 모아서 실력 지표로 사용한다.
|
||||
- ASSIST/EASY/NORMAL/HARD/EX HARD/FULL COMBO처럼 클리어 상태가 수집 진행도를 만든다.
|
||||
- 단위인정에는 합격 여부와 최고 달성률이 별도로 남는다.
|
||||
|
||||
ProjectQuint에 적용할 점: 점수와 clear lamp를 분리하고, 단위인정을 숫자 레이팅에 합산하지
|
||||
않으며, 팩·난이도표별 수집률을 제공한다.
|
||||
|
||||
### SOUND VOLTEX
|
||||
|
||||
- Skill Analyzer는 정해진 여러 곡을 연속으로 통과하는 인증이며, 획득한 가장 높은 Skill
|
||||
Level을 프로필에 표시한다.
|
||||
- 같은 실력을 숫자 하나로만 표현하지 않고 코스 칭호와 달성률을 함께 사용한다.
|
||||
|
||||
ProjectQuint에 적용할 점: AERY 등 난이도표 제공자의 단위인정을 독립된 인증 배지로 보존한다.
|
||||
|
||||
### DJMAX RESPECT V
|
||||
|
||||
- 플레이 누적형 Clear Pass와 프로필 카드·플레이트 등 코스메틱 보상을 사용한다.
|
||||
- 시즌·유료 부스터·곡 해금도 포함하지만 이는 서버 없는 범용 BMS 구동기와 맞지 않는다.
|
||||
|
||||
ProjectQuint에 적용할 점: 플레이트, 프레임, 칭호 같은 꾸미기 보상만 차용한다. 시즌 만료,
|
||||
FOMO, 유료 부스터, BMS 채보 잠금은 도입하지 않는다.
|
||||
|
||||
### B30 계열 레이팅
|
||||
|
||||
- Arcaea 커뮤니티가 분석한 Potential은 세밀한 chart constant와 플레이 성과를 결합하며,
|
||||
`Best 30`이 핵심 기록 풀이다.
|
||||
- CHUNITHM도 공식적으로 버전별 rating 대상곡 풀을 운영하고, 높은 곡별 성과를 모아 rating을
|
||||
구성한다.
|
||||
|
||||
ProjectQuint에는 최근 기록을 강제로 섞어 rating이 내려가는 구조를 도입하지 않는다. 한 채보의
|
||||
최고 성과 하나만 보존한 `Best 30` 산술평균을 사용한다.
|
||||
|
||||
## 3. Profile Level: 누적 활동 성장
|
||||
|
||||
### 3.1 XP 지급 조건
|
||||
|
||||
`Valid Play`만 XP를 지급한다.
|
||||
|
||||
- 곡을 정상 완주했거나,
|
||||
- 게임오버 시점까지 active play time이 30초 이상이고 판정 단위가 20개 이상인 플레이
|
||||
- 로딩 취소, 시작 직후 retry/quit, 자동 프로파일링 플레이는 XP 0
|
||||
- pause와 loading 시간은 active play time에서 제외
|
||||
- 동일한 `PlayEventId`는 정확히 한 번만 반영
|
||||
|
||||
오프라인 코스메틱 수치이므로 사용자의 반복 연습을 억제하는 일일 제한은 두지 않는다.
|
||||
대신 비정상적으로 짧은 재시작과 크래시 후 중복 반영만 방지한다.
|
||||
|
||||
### 3.2 플레이 XP 산식
|
||||
|
||||
```text
|
||||
BaseXP = clamp(
|
||||
20
|
||||
+ floor(ActivePlaySeconds / 10) * 4
|
||||
+ floor(JudgedUnits / 100) * 2,
|
||||
20,
|
||||
100)
|
||||
|
||||
ClearBonus = GameOver 0 / Normal Clear 20 / Hard Clear 30
|
||||
PlayXP = BaseXP + ClearBonus + NewlyEarnedMilestoneXP
|
||||
```
|
||||
|
||||
`JudgedUnits`에는 일반 노트와 롱노트 tick을 포함한다. 밀도가 높은 채보만으로 XP를 과도하게
|
||||
얻지 않도록 노트 기여분과 전체 BaseXP를 제한한다. 채보 작성자가 임의로 적는 `#PLAYLEVEL`은
|
||||
XP에 사용하지 않는다.
|
||||
|
||||
곡별 최초 마일스톤은 각각 한 번만 지급한다.
|
||||
|
||||
| 마일스톤 | XP |
|
||||
|---|---:|
|
||||
| 최초 Valid Play | 20 |
|
||||
| 최초 Normal Clear | 40 |
|
||||
| 최초 Hard Clear | 60 |
|
||||
| 최초 Full Combo | 80 |
|
||||
| 최초 All Pure 이론치 | 120 |
|
||||
| 단위인정 코스 최초 합격 | 250 |
|
||||
|
||||
### 3.3 레벨 곡선
|
||||
|
||||
리듬게임에 공통으로 쓰이는 하나의 XP 공식은 없다. osu!는 누적 total score에 cubic·exponential
|
||||
항을 결합한 고유 공식을 사용하고, DJMAX를 비롯한 상업 게임 다수는 정확한 DJ LEVEL 산식을
|
||||
공개하지 않는다. 따라서 아래 식은 업계 표준을 복제한 것이 아니라 ProjectQuint의 목표 플레이
|
||||
횟수에 맞춘 투명한 v1 곡선이다.
|
||||
|
||||
현재 레벨 `L`에서 다음 레벨까지 필요한 XP와 누적 XP는 다음과 같다.
|
||||
|
||||
```text
|
||||
NextXP(L) = 40L + 30
|
||||
TotalXP(L) = 20 * (L - 1)^2 + 50 * (L - 1)
|
||||
```
|
||||
|
||||
`NextXP`가 레벨마다 선형으로 증가하므로 누적값은 2차식이 된다. 초반에는 한두 번의 플레이로
|
||||
빠르게 오르고, 중후반에는 한 레벨에 여러 세션이 필요하게 만드는 일반적인 완만한 성장 형태다.
|
||||
|
||||
| 레벨 | 누적 XP | 평균 100 XP 플레이 기준 |
|
||||
|---:|---:|---:|
|
||||
| 2 | 70 | 약 1회 |
|
||||
| 10 | 2,070 | 약 21회 |
|
||||
| 30 | 18,270 | 약 183회 |
|
||||
| 50 | 50,470 | 약 505회 |
|
||||
| 100 | 200,970 | 약 2,010회 |
|
||||
|
||||
레벨 자체에는 상한을 두지 않는다. 초기 코스메틱 보상은 100레벨까지만 준비하고 이후에는
|
||||
레벨 숫자와 XP 진행만 계속된다. 실제 플레이 로그 분포를 확보한 뒤 계수만 조정할 수 있도록
|
||||
`XpRulesVersion`을 둔다. 이미 받은 XP는 규칙 변경으로 소급 삭감하지 않는다.
|
||||
|
||||
## 4. Rating: Best 30 플레이 기록
|
||||
|
||||
### 4.1 분리 단위
|
||||
|
||||
Rating은 별도의 스킬 트리나 스킬 시스템이 아니다. 난이도 상수가 부여된 채보를 높은 정확도로
|
||||
클리어한 기록 중 가장 좋은 30개를 숫자로 요약한다.
|
||||
|
||||
Rating은 다음 조합마다 독립적으로 계산한다.
|
||||
|
||||
```text
|
||||
DifficultyTableId × KeyCount × ScratchMode
|
||||
```
|
||||
|
||||
표시 단위는 `4K`, `5K`, `6K`, `7K`, `1+4`, `1+5`, `1+6`, `1+7`이다. `1+7`은 스크래치
|
||||
1레인과 건반 7레인을 합친 전통적인 8레인 BMS 형태다. AERY·Stella 같은 난이도표는 서로 다른
|
||||
난이도 체계이므로 숫자가 같더라도 하나의 Best 30에 섞지 않는다. 팩은 파일 묶음일 뿐 Rating
|
||||
분리 축이 아니다. 같은 채보가 여러 난이도표에 등재되면 chart hash 하나에 난이도표별 최고
|
||||
performance snapshot을 독립적으로 보존한다. 서로 다른 키 형태나 난이도표를 합친 Global Rating은
|
||||
만들지 않는다.
|
||||
|
||||
- 난이도표가 명시한 순서와 rating constant mapping이 있는 채보만 공식 Rating 대상
|
||||
- `#PLAYLEVEL`만 있는 채보는 `UNRANKED`로 기록·수집하되 Rating에서 제외
|
||||
- 레이더 특성치는 채보 성향이며 난이도 수치가 아니므로 레이팅에 직접 사용하지 않음
|
||||
- 특수/GIMMICK 레벨은 provider가 숫자 mapping을 제공할 때만 포함
|
||||
|
||||
### 4.2 채보 상수
|
||||
|
||||
난이도표 표기를 다음처럼 `ChartConstant`로 변환한다.
|
||||
|
||||
| 난이도 표기 | ChartConstant |
|
||||
|---|---:|
|
||||
| LEVEL 14 | 14.0 |
|
||||
| LEVEL 14+ | 14.5 |
|
||||
| LEVEL 15 | 15.0 |
|
||||
| LEVEL 15+ | 15.5 |
|
||||
| LEVEL 16 | 16.0 |
|
||||
| LEVEL 16+ | 16.5 |
|
||||
|
||||
다른 난이도표의 `★`, `sl`, `st` 등은 provider별 rating map에서 명시적으로 대응한다. 대응표가
|
||||
없는 provider를 임의로 AERY 숫자에 끼워 맞추지 않는다.
|
||||
|
||||
### 4.3 한 플레이의 Play Rating
|
||||
|
||||
Rating 대상이 되려면 해당 플레이가 Normal 이상으로 clear되어야 한다. Game Over 기록은 점수와
|
||||
collection에는 남지만 Rating 후보에서는 제외한다.
|
||||
|
||||
```text
|
||||
GaugeBonus = Hard Clear ? 0.50 : 0.00
|
||||
PlayRating = ChartConstant × AccuracyFactor + GaugeBonus
|
||||
```
|
||||
|
||||
`AccuracyFactor`는 아래 anchor 사이를 선형 보간한다.
|
||||
|
||||
| 정확도 | 계수 |
|
||||
|---:|---:|
|
||||
| 0% | 0.00 |
|
||||
| 80% | 0.30 |
|
||||
| 90% | 0.60 |
|
||||
| 95% | 0.80 |
|
||||
| 97% | 0.88 |
|
||||
| 98% | 0.93 |
|
||||
| 99% | 0.97 |
|
||||
| 99.5% | 0.985 |
|
||||
| 100% | 1.00 |
|
||||
|
||||
0~80% 구간도 두 anchor 사이를 선형 보간하므로 경계에서 값이 갑자기 튀지 않는다. 계산은
|
||||
`double` 정밀도로 유지하고 프로필·결과 화면에서만 소수점 셋째 자리까지 반올림한다.
|
||||
|
||||
예시:
|
||||
|
||||
| 채보 | 정확도 | PlayRating |
|
||||
|---|---:|---:|
|
||||
| LEVEL 15 (15.0), Normal | 98.00% | 13.950 |
|
||||
| LEVEL 15 (15.0), Normal | 99.00% | 14.550 |
|
||||
| LEVEL 15 (15.0), Hard | 99.00% | 15.050 |
|
||||
| LEVEL 15+ (15.5), Normal | 99.00% | 15.035 |
|
||||
| LEVEL 16 (16.0), Normal | 98.00% | 14.880 |
|
||||
| LEVEL 16 (16.0), Hard | 100.00% | 16.500 |
|
||||
|
||||
Hard Clear는 같은 채보·정확도의 Normal Clear보다 정확히 `+0.50` 높은 PlayRating을 받는다.
|
||||
이는 `LEVEL N` Hard Clear를 동일 정확도의 `LEVEL N+` Normal Clear와 동급으로 취급한다는
|
||||
명시적인 정책이다. 배율이 아니라 고정 가산값이므로 난이도에 따라 보너스가 변하지 않으며,
|
||||
Best 30을 모두 Hard Clear했을 때 최종 Rating 차이도 정확히 `+0.50`이다. Full Combo와 All
|
||||
Pure는 clear lamp와 collection에서 별도로 표현하고 Rating 보너스는 주지 않는다. Pure 보너스가
|
||||
포함된 raw score도 길이가 긴 채보에 유리하므로 Rating에 직접 넣지 않는다.
|
||||
|
||||
### 4.4 Best 30 집계
|
||||
|
||||
같은 chart hash에서는 가장 높은 eligible PlayRating 하나만 사용한다. 이를 높은 순서로 정렬해
|
||||
상위 30개를 단순 평균한다. 보유 기록이 30개 미만이면 비어 있는 slot은 0이다.
|
||||
|
||||
```text
|
||||
Rating = sum(BestPlayRating[0..29]) / 30
|
||||
```
|
||||
|
||||
고정된 30 slot은 한 곡만 잘해서 곧바로 높은 Rating을 얻는 것을 막는다. 더 높은 PlayRating이
|
||||
들어오면 기존 30위 기록을 교체하므로 같은 rules/table revision 안에서는 Rating이 절대
|
||||
감소하지 않는다. UI에는 `AERY 5K RATING 14.35 · B30 COMPLETE` 또는
|
||||
`STELLA 1+7 RATING 13.80 · B12 / 30`처럼 표시한다.
|
||||
|
||||
예를 들어 Best 30이 각각 다음 10곡씩이라면:
|
||||
|
||||
```text
|
||||
10 × (LEVEL 15.0, 99.00%) = 10 × 14.550
|
||||
10 × (LEVEL 15.5, 98.00%) = 10 × 14.415
|
||||
10 × (LEVEL 16.0, 97.00%) = 10 × 14.080
|
||||
|
||||
Rating = (145.50 + 144.15 + 140.80) / 30 = 14.3483...
|
||||
표시값 = 14.348
|
||||
```
|
||||
|
||||
초기 modifier 정책:
|
||||
|
||||
- OFF, MIRROR: Rating 포함
|
||||
- RANDOM: 기록과 clear lamp는 보존하지만 Rating에서는 제외하고 `Experimental`로 표시
|
||||
- 단위인정: 기존 정책대로 OFF/MIRROR만 허용
|
||||
|
||||
RANDOM은 배치에 따라 실제 난이도가 달라지므로 chart feature 기반 보정이 검증되기 전까지
|
||||
공식 수치에 섞지 않는다.
|
||||
|
||||
### 4.5 버전과 재계산
|
||||
|
||||
- `RatingRulesVersion`과 difficulty table revision을 Rating 결과에 표시
|
||||
- 레이팅은 원본 플레이 기록에서 언제든 재계산 가능한 materialized view
|
||||
- 산식 또는 난이도표가 바뀌면 레이팅은 오르내릴 수 있지만 Profile XP와 해금 보상은 유지
|
||||
- 과거 산식의 숫자는 기록 화면에서 선택적으로 보존하되 현재 프로필에는 최신 산식만 표시
|
||||
|
||||
## 5. Course Certification
|
||||
|
||||
- 난이도표 provider마다 최고 합격 단위, 최고 달성률, 최고 잔여 게이지, 합격 횟수, 시도 횟수,
|
||||
사용 modifier를 저장한다.
|
||||
- 코스를 처음 합격하면 해당 `DifficultyProviderId × CourseId`의 Certification Badge를 획득한다.
|
||||
배지는 별도 중복 저장하지 않고 영구 course clear 기록에서 파생하므로 프로필 export/import와
|
||||
구버전 기록에도 자동 적용된다. 재합격은 정확도·트로피 기록만 갱신하고 같은 배지를 중복 지급하지 않는다.
|
||||
- 획득한 배지 중 하나를 Featured Badge로 선택할 수 있다. 선택한 course ID는 로컬 profile document에
|
||||
저장하고 Home 프로필 카드와 Profile Overview에 표시한다. 보유하지 않은 배지는 선택할 수 없으며,
|
||||
저장된 배지를 찾을 수 없을 때는 남아 있는 첫 배지를 안전한 표시값으로 사용한다.
|
||||
- 단위 배지는 Rating에 더하지 않는다. 숫자로 표현하기 어려운 연속 플레이 안정성을
|
||||
증명하는 별도의 칭호다.
|
||||
- 같은 단위의 반복 합격은 XP farm이 되지 않으며 최초 합격 XP만 지급한다.
|
||||
- 현재 `AeryCourseRecord` 중심 구조는 장기적으로 `CourseRecord`와 provider ID 기반 구조로
|
||||
일반화한다.
|
||||
|
||||
## 6. Chart Mastery와 Collection
|
||||
|
||||
곡별 성취 단계:
|
||||
|
||||
```text
|
||||
UNPLAYED < FAILED < NORMAL CLEAR < HARD CLEAR < FULL COMBO < ALL PURE
|
||||
```
|
||||
|
||||
- 화면에는 최고 lamp 하나를 표시하되, 저장은 `ClearMilestoneFlags`로 각 마일스톤을 보존한다.
|
||||
- Game Over 기록이 기존 clear를 덮어쓰지 않는다.
|
||||
- pack, difficulty provider, key mode별로 Played/Clear/Hard/FC/All Pure 수와 비율을 계산한다.
|
||||
- 같은 chart hash는 전역 수집에서 한 번만 세지만, 여러 pack에 포함된 경우 각 pack의 완성도에는
|
||||
해당 pack entry로 표시할 수 있다.
|
||||
- pack을 분리하거나 삭제해도 chart hash 기반 기록은 보존하며, 다시 연결되면 복구한다.
|
||||
- Favorite은 프로필별 수집 데이터로 취급한다.
|
||||
|
||||
## 7. Achievements와 보상
|
||||
|
||||
초기 achievement 범주는 다음으로 제한한다.
|
||||
|
||||
- 첫 플레이, 첫 clear, 첫 hard clear, 첫 full combo, 첫 이론치
|
||||
- 4K/5K/6K/7K 각 모드의 첫 clear와 누적 clear 수
|
||||
- 단위인정 최초 합격과 상위 단위 합격
|
||||
- pack별 25%/50%/75%/100% collection
|
||||
- 누적 100/500/1,000곡 플레이, 1,000 combo 등 명확한 장기 기록
|
||||
|
||||
보상 종류:
|
||||
|
||||
- Profile Frame
|
||||
- Plate / Profile Card 배경
|
||||
- Title / Badge
|
||||
- Character 또는 character animation variant
|
||||
- Gear color palette / Note skin
|
||||
|
||||
보상은 전부 시각 요소다. 판정 폭, 게이지, 점수 배율, 키음 처리에는 영향을 주지 않는다.
|
||||
기간 한정 시즌, 로그인 streak, daily quest는 도입하지 않는다.
|
||||
|
||||
레벨 보상 cadence는 5레벨마다 하나를 기본으로 하고, 10/25/50/75/100에서 큰 profile frame이나
|
||||
character variant를 제공한다. 실제 asset이 없는 reward ID는 UI에 가짜 보상으로 노출하지 않고
|
||||
`Coming Soon` placeholder도 만들지 않는다.
|
||||
|
||||
## 8. 로컬 데이터 설계
|
||||
|
||||
### 8.1 프로필 경계
|
||||
|
||||
처음에는 한 개의 로컬 프로필만 자동 생성하지만, 저장 경로는 처음부터 복수 프로필을 지원한다.
|
||||
|
||||
```text
|
||||
ProjectQuint/
|
||||
profiles.json
|
||||
profiles/{ProfileId}/
|
||||
profile.json
|
||||
progression.json
|
||||
records.json
|
||||
courses.json
|
||||
favorites.json
|
||||
pending-play-commit.json
|
||||
```
|
||||
|
||||
팩 registry, difficulty table, audio/video cache와 장치 설정은 전역으로 유지한다. 플레이 기록,
|
||||
Favorite, course, XP, achievement와 장착한 cosmetic은 프로필에 귀속한다.
|
||||
|
||||
기존 전역 `records.json`, `courses.json`, `favorites.json`은 최초 기본 프로필로 한 번만 복사한 뒤
|
||||
migration marker를 저장한다. 원본은 자동 삭제하지 않는다.
|
||||
|
||||
구현 상태: 기본 프로필 `local-player`에 대해 위 경계가 적용되었다. 시작 시
|
||||
`profile-storage-migration.json`을 확인하고, marker가 없으면 전역 primary와 `.bak`을 대상 파일이
|
||||
없는 경우에만 같은 볼륨의 임시 파일을 거쳐 원자 복사한다. 부분 완료 후 재실행해도 이미 만들어진
|
||||
프로필 파일은 덮어쓰지 않으며, 모든 복사 단계가 끝난 뒤에만 marker를 저장한다.
|
||||
|
||||
### 8.2 ProfileDocument
|
||||
|
||||
```text
|
||||
SchemaVersion
|
||||
ProfileId
|
||||
DisplayName
|
||||
CreatedUtcTicks
|
||||
LastPlayedUtcTicks
|
||||
TotalXp
|
||||
XpRulesVersion
|
||||
LifetimeStatsByMode[]
|
||||
UnlockedRewardIds[]
|
||||
AchievementStates[]
|
||||
EquippedCosmetics
|
||||
MigrationState
|
||||
```
|
||||
|
||||
Level은 `TotalXp`에서 계산되는 파생값이며 저장값을 정본으로 삼지 않는다.
|
||||
|
||||
### 8.3 PlayResultEvent
|
||||
|
||||
완료된 한 플레이는 불변 event로 만든다.
|
||||
|
||||
```text
|
||||
EventId (UUID)
|
||||
ChartId (content hash)
|
||||
PackId
|
||||
KeyCount / HasScratch
|
||||
DifficultyProviderId / Revision / NormalizedDifficulty
|
||||
ScoringRulesVersion / JudgementRulesVersion
|
||||
LaneModifier / GaugeType
|
||||
Completed / GameOver / ClearMilestones
|
||||
Score / Accuracy / MaxCombo
|
||||
Kool / PureKool / Cool / Good / Bad / Miss / Fast / Late
|
||||
JudgedUnits / LongNoteTicks
|
||||
ActivePlaySeconds
|
||||
OccurredUtcTicks (표시용, 규칙 판정에 사용하지 않음)
|
||||
```
|
||||
|
||||
현재 `records.json v3`는 `BestScore`, `BestAccuracyPercent`, `BestMaxCombo`를 각각 독립적으로
|
||||
갱신하므로 서로 다른 플레이의 값이 합쳐질 수 있다. Rating에는 이를 사용할 수 없다.
|
||||
다음 record schema에는 한 플레이의 값이 함께 들어 있는 `BestPerformanceSnapshot`과
|
||||
`PureKoolCount`, `JudgedUnits`, `ClearState`, modifier, rules version을 추가해야 한다.
|
||||
|
||||
### 8.4 원자적 결과 commit
|
||||
|
||||
결과 저장 중 강제 종료되어 XP나 PlayCount가 두 번 오르는 것을 막는다.
|
||||
|
||||
1. `pending-play-commit.json`에 PlayResultEvent를 원자 저장
|
||||
2. song record에 같은 EventId가 적용됐는지 확인 후 반영
|
||||
3. course record가 있으면 같은 방식으로 반영
|
||||
4. progression XP·achievement에 EventId가 적용됐는지 확인 후 반영
|
||||
5. 모든 저장이 성공한 뒤 pending commit 완료 처리
|
||||
6. 다음 실행 시 pending commit이 있으면 같은 EventId로 재개
|
||||
|
||||
각 repository는 EventId에 대해 idempotent해야 한다. 기존 `AtomicJsonFile<T>`의 primary/backup
|
||||
복구 규칙을 그대로 사용한다.
|
||||
|
||||
Rating과 collection 통계는 records/course에서 재계산 가능한 cache로 취급한다.
|
||||
Profile XP receipt와 이미 해금된 cosmetic은 소급 재계산하지 않는 정본이다.
|
||||
|
||||
## 9. 화면 정보 구조
|
||||
|
||||
UI 전면 개편 단계에서 다음 정보 구조만 유지한다.
|
||||
|
||||
- Home: Profile Lv., XP bar, 최근 플레이한 난이도표·키 형태의 Rating, 최고 단위 배지
|
||||
- Music Select: 곡별 최고 lamp, 최고 기록, 해당 플레이가 rating top 30에 포함되는지 표시
|
||||
- Result: 점수 결산 후 XP 획득, 새 record, rating 변화, 새 milestone을 순서대로 표시
|
||||
- Profile / Overview: 활동 레벨, 플레이 시간, 전체 collection, 최근 achievement
|
||||
- Profile / Rating: 위/아래 키로 난이도표 × 4K/5K/6K/7K 및 1+4/1+5/1+6/1+7 조합을 순환하며 Rating과 Best 30 확인
|
||||
- Profile / Courses: 획득한 난이도표별 Certification Badge와 전체 코스 이력·최고 달성률
|
||||
- Profile / Collection: pack·난이도·키 모드별 clear lamp 집계
|
||||
- Profile / Achievements: 획득 조건과 해금 cosmetic
|
||||
|
||||
`PROFILE LV.`와 `AERY 5K RATING` 또는 `STELLA 1+7 RATING`이라는 명칭을 명확히 사용해 활동 레벨과 Rating을 혼동하지 않게 한다.
|
||||
|
||||
구현 상태: Profile modal은 `OVERVIEW / RATING / COLLECTION / COURSES` 네 탭을 제공한다.
|
||||
Collection은 아직 스캔된 전체 팩의 완주율이 아니라 **한 번 이상 기록된 고유 채보**를 분모로 삼으며,
|
||||
이를 UI에서 `RECORDED CHARTS`로 명시한다. 최종 clear lamp와 별개로 Full Combo 및 All Pure의
|
||||
채보별 최초 달성 상태는 계속 보존한다. Courses는 현재 AERY 단위 기록을 공통 collection snapshot으로
|
||||
투영하며, 이후 generic course provider도 같은 화면 모델에 연결할 수 있다.
|
||||
레벨업 연출은 플레이 중 HUD에 띄우지 않고 Result 또는 Home 복귀 시에만 보여준다.
|
||||
|
||||
## 10. 초기 구현 순서
|
||||
|
||||
1. 기록 schema v4와 `PlayResultEvent`, consistent best performance snapshot
|
||||
2. profile directory와 기존 전역 기록의 비파괴 migration
|
||||
3. pending commit coordinator와 EventId idempotency
|
||||
4. Profile XP, level curve, lifetime stats
|
||||
5. generic course certification과 collection materialized view
|
||||
6. 난이도표 × 키 형태별 Rating v3와 Best 30 상세 화면용 모델
|
||||
7. achievement catalog와 cosmetic inventory
|
||||
8. UI 전면 개편 시 Profile 화면과 Result 성장 연출 연결
|
||||
|
||||
## 11. 검증 기준
|
||||
|
||||
- 동일 PlayEventId를 2회 적용해도 XP, PlayCount, achievement가 한 번만 증가한다.
|
||||
- 결과 저장 단계마다 강제 종료한 뒤 재실행해 records와 progression이 같은 event를 반영한다.
|
||||
- 같은 replay와 같은 규칙 버전은 항상 같은 PlayRating을 만든다.
|
||||
- 같은 채보·정확도에서 Hard Clear PlayRating은 Normal Clear보다 정확히 0.50 높다.
|
||||
- 새 기록 추가 또는 기존 기록 개선만으로 같은 rules/table revision의 Rating이 감소하지 않는다.
|
||||
- AERY와 다른 난이도표의 같은 숫자가 하나의 레이팅에 합산되지 않는다.
|
||||
- 4K/5K/6K/7K와 scratch 기록이 서로 섞이지 않는다.
|
||||
- pack 이동·이름 변경·재등록 후에도 같은 content hash 기록과 마일스톤이 복구된다.
|
||||
- RANDOM 플레이는 v1 공식 레이팅에 들어가지 않지만 점수·lamp·collection은 사라지지 않는다.
|
||||
- 점수 산식이 바뀌어도 과거 XP와 cosmetic은 유지되고 Rating만 최신 규칙으로 재계산된다.
|
||||
- primary JSON 손상 시 backup 복구가 되며 export/import 후 EventId가 중복 지급되지 않는다.
|
||||
- 프로필 초기화와 곡 기록 초기화는 서로 다른 명시적 확인 절차를 가진다.
|
||||
|
||||
## 12. 참고 자료
|
||||
|
||||
- osu! Performance points: https://osu.ppy.sh/wiki/en/Performance_points
|
||||
- osu! pp weighting: https://osu.ppy.sh/wiki/en/Performance_points/Weighting_system
|
||||
- osu! FAQ (Profile Level과 pp 분리): https://osu.ppy.sh/wiki/en/FAQ
|
||||
- osu! Profile Level 누적 점수 공식:
|
||||
https://osu.ppy.sh/wiki/en/Gameplay/Score/Total_score
|
||||
- Arcaea community-documented chart constant와 Best 30:
|
||||
https://arcaea.fandom.com/wiki/Potential
|
||||
- CHUNITHM 공식 Rating 대상곡 구성 변경:
|
||||
https://info-chunithm.sega.com/3299/
|
||||
- beatmania IIDX play data, clear lamp, EX SCORE, 단위 기록:
|
||||
https://p.eagate.573.jp/game/2dx/26/howto/epass/play_data.html
|
||||
- beatmania IIDX DJ RANK 상위 성과 집계:
|
||||
https://p.eagate.573.jp/game/2dx/26/howto/epass/dj_rank.html
|
||||
- SOUND VOLTEX Skill Analyzer:
|
||||
https://p.eagate.573.jp/game/sdvx/v/p/howto/skill.html
|
||||
- DJMAX RESPECT V official Steam announcements:
|
||||
https://steamcommunity.com/app/960170/announcements/
|
||||
Reference in New Issue
Block a user