docs(rogue-map): P8 설계 — 절차 생성 맵·층 시스템·유물 방·점선 맵 UI

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-12 08:01:29 +09:00
parent b5a648fc23
commit 52d808eacd

View File

@@ -0,0 +1,92 @@
# P8 — 로그라이크 절차 생성 맵·층 시스템·유물 방 설계
날짜: 2026-06-12 (사용자 승인 완료)
브랜치: `feature/p8-rogue-map`
선행: P7 (유물 19종 — 유물 방이 `PickNewRelic` 재사용)
## 범위
1. **절차 생성 맵** — 막 시작마다 8층×최대 4열 DAG를 Lua 런타임 생성 (런·막마다 다른 맵). `data/map.json` 정적 맵 제거
2. **층(depth) 시스템** — 노드를 지날 때마다 층 +1 (층 = 행), 층별 노드 타입 등장 규칙
3. **유물 방(보물 노드)** — 상자 열리는 모션 + 유물(`PickNewRelic`)·메소 획득, 노드 타입 `treasure` 추가
4. **맵 UI 정비** — 노드 연결 점선(도트 3개 보간), 타입별 아이콘 색/라벨, 상태 4단(방문/현재/도달 가능/잠김)
5. **메소 표기** — 화폐 표시 텍스트 "골드" → "메소" (메이플 IP, 표시만 — 내부 변수 Gold 유지)
비범위: 이벤트 노드(?), 경로 교차 방지(시각 교차 허용 — 점선이라 식별 가능), 저장.
## 절차 생성 알고리즘 (StS 방식)
그리드: 행 1~7 × 열 1~4 + 8행 보스 단일 노드. 노드 id = `"r{row}c{col}"`, 보스 = `"boss"`.
1. 시작 열: {1,2,3,4} 셔플 후 앞 2개 = 경로 1·2의 시작(서로 다름 보장), 경로 3·4는 랜덤
2. 경로 4개를 각각 1행→7행으로 걸어 올림: 다음 열 = `clamp(열 + random(-1..1), 1, 4)`. 지나는 칸마다 노드 생성(중복은 병합), `(r,c) → (r+1,c')` 간선 추가(중복 간선 병합)
3. 7행의 모든 노드 → 보스 간선
4. `MapStart` = 1행에 생성된 노드들
### 타입 배정 (행 오름차순)
| 행 | 규칙 |
|---|---|
| 1~2행 | combat 고정 |
| 3행 | combat 45 / shop 12 / rest 12 (가중 추첨, 합계로 정규화) |
| 4~6행 | combat 45 / elite 16 / shop 12 / rest 12 / treasure 15 |
| 7행 (보스 직전) | rest 50 / combat 25 / shop 10 / elite 8 / treasure 7 |
| 8행 | boss 고정 |
추가 제약: **부모(간선으로 들어오는 이전 행 노드) 중 elite가 있으면 해당 노드는 elite 금지** (연속 엘리트 방지).
### 층 카운터
`Depth` prop: `PickNode``Depth = node.row`. 막 전환·런 시작 시 0. TopBar Floor 텍스트를 `막 F/3 · D층`으로 확장.
### 노드 enemy 필드 제거
몬스터는 P4 이후 `node.type` 그룹 필터(`BuildMonsters`)로 결정되므로 `enemy` 필드·`CurrentEnemyId` 대입은 제거(`CurrentEnemyId = ""` 유지). `data/map.json`·`luaMapNodesTable`·`luaStartArray`·`MAX_ROW` 제거.
## 유물 방 (TreasureHud)
- `PickNode` 분기에 `treasure``ShowTreasure` 추가 (`ShowState("treasure")`·`HideGameHud` 등록)
- UI: 어두운 패널 + 타이틀("보물 상자") + 중앙 상자 버튼(닫힌 상자 RUID) + 보상 텍스트(초기 숨김) + 나가기 버튼
- `OpenChest`: 1회 가드(`ChestOpened`) → **흔들림 모션**(anchoredPosition ±8px, 0.08s × 6스텝 타이머 체인) → **열린 상자 RUID로 교체** → 보상 지급·표시
- 메소: `40 + random(0..20)`
- 유물: `PickNewRelic()` — 미보유 추첨, 전부 보유 시 메소 +30 대체
- 보상 텍스트 예: `유물 획득: 황소 투구 · 메소 +52`
- 상자 닫힘/열림 스프라이트는 공식 maplestory 리소스 검색("상자"/"보물상자") 후 메이커 선별
- 나가기 → `LeaveNode`(기존 → ShowMap)
## 맵 UI
### 정적 그리드 (생성기, MapHud 섹션 재작성)
- 노드 버튼 28개(`Node_r{1..7}c{1..4}`, 56×56) + `Node_boss`(72×72, 상단 중앙) + 각 노드 `Label`(타입 한글)
- 배치: 행 y = `-330 + (row-1)*105` (보스 y=405), 열 x = `-270 + (col-1)*180`, 보스 x=0
- 점선 도트: 모든 가능 간선(행 1~6: `c→c±1/c`, 행 7→보스)에 대해 도트 3개(8×8, t=0.25/0.5/0.75 보간 위치). 엔티티 `Dot_r{r}c{c}_{c'}_{k}` (보스행은 `Dot_r7c{c}_b_{k}`)
- 모든 노드·도트는 기본 비활성, `RenderMap`이 토글
### RenderMap 재작성 (상태 4단 + 점선)
- 노드: 맵에 없으면 숨김. 있으면 Label = 타입명, 색:
- 방문(`VisitedNodes`에 포함) → 어둡게 `(0.18,0.19,0.22)`
- 현재 위치 → 골드 `(0.95,0.8,0.3)`
- 도달 가능(IsReachable) → 타입색 밝게 + 버튼 활성
- 잠김 → 타입색 45% 어둡게 + 버튼 비활성
- 타입색: 전투 `(0.78,0.36,0.32)` / 엘리트 `(0.62,0.4,0.85)` / 상점 `(0.9,0.75,0.35)` / 휴식 `(0.4,0.75,0.45)` / 보물 `(0.35,0.7,0.75)` / 보스 `(0.85,0.25,0.25)`
- 도트: 간선 존재 시 표시. 현재 노드(또는 시작 전 1행 진입선)에서 나가는 간선 = 골드, 그 외 = 회색 `(0.5,0.5,0.55)`
- `VisitedNodes`(any prop): PickNode 시 추가
### 메소 표기
표시 문자열 "골드" → "메소": TopBar Gold·ShopHud Gold·상점 가격(`N 메소`)·유물 소진 토스트. 내부 prop `Gold` 유지.
## 검증
1. **JS 미러 + 단위 테스트**: `tools/map/rogue-map.mjs``generateMap(rng)` 동일 로직(시드 PRNG 주입) + `rogue-map.test.mjs` — 모든 노드가 시작점에서 도달 가능·보스 수렴·1~2행 combat만·elite/treasure 4행부터·간선 인접 열만·elite 부모 연속 금지·결정성(동일 시드 동일 맵). ⚠️ Lua `GenerateMap`과 로직 동기화 유지(sim-balance 패턴)
2. 기존 `sim-balance` 21건 유지 (맵과 무관)
3. 메이커: 빌드 0에러, 플레이테스트 — 맵 생성/점선/상태색, 노드 진행(층 증가), 유물 방 상자 연출·보상, 보스 클리어 → 다음 막 새 맵
## 결정 사항
- 경로 4개·8층×4열 (사용자 승인 규모)
- 점선 도트 방식 채택 (UI 회전 리스크 회피, StS 원작 미감)
- 시각적 간선 교차는 허용 (점선이라 추적 가능 — YAGNI)
- `RUN_LENGTH`/`ACT_MAPS` 막 시스템은 변경 없음 (막마다 새 맵 생성만 추가)