Files
maplecontest/docs/superpowers/specs/2026-06-12-rogue-map-design.md

93 lines
5.7 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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` 막 시스템은 변경 없음 (막마다 새 맵 생성만 추가)