---
type: 설계_문서
scope: 맵_시스템 (절차 생성 + 시각 시뮬레이터 + 리소스 카탈로그)
author: 개발팀장 (dev-team-lead)
date: 2026-05-18
version: v1.0 (BT13-Dev-Map Phase 1 — 단일 SOT)
project: EerieVillage (기묘한 고을 : 조선퇴마뎐)
phase: BT13-Dev-Map Phase 1 (개발팀장 Opus 설계)
data_source: |
  BT5-Dev 인수인계 2026-05-07·2026-05-08 (발판 시스템 BT47·BT75·몬스터 BT76~BT109)·
  E:/EerieVillage/Assets/Scripts/Mechanics/{KinematicObject,PlayerController,EnemyController,GameOptimizer,SpawnPoint,MonsterRandomizer}.cs·
  E:/EerieVillage/Assets/Scripts/Model/PlatformerModel.cs·
  E:/EerieVillage/Assets/Prefabs/{Player,Enemy,Enemy_M001~M006}.prefab·
  E:/EerieVillage/Assets/Tiles/{TileFloating*,TileGround*,cloud,fence,ShortBuilding,TallBuilding,MidgroundFiller}.asset·
  E:/EerieVillage/ProjectSettings/Physics2DSettings.asset·
  프로젝트/EerieVillage/기획/05_스테이지_구조_초안.md v0.1·
  프로젝트/EerieVillage/기획/level/01_스테이지_구조.md v0.1·
  프로젝트/EerieVillage/개발/spec/스킬_시스템_설계_v1.md v1.0
status: Phase 1 설계 완료 — Phase 2 코드 구현 분할 권고 + PD 결정 대기 안건 11건 (§15 정합·2026-05-18 PM 정정·dev-team-lead 자기 검증 누락 보완)
---

# 맵 시스템 설계 v1 (BT13-Dev-Map)

## §1. 요구 분석 + PD 결정 4건 정합 확인

### 1-1. PD 핵심 요구 (2026-05-18 원문 7항)

> "이제 나는 우리 게임의 맵을 제작하고 싶어.
> * 일반적인 플랫포머 게임의 맵 제작 방식을 고려해 맵을 만들고 싶어.
> * 하지만 맵의 길이만 지정하면, 랜덤으로 자동으로 생성하는 형태의 맵 시스템을 만들고 싶어.
> * 맵에는 자동으로 몬스터가 등장하게 될 생성 포인트(거점)이 배치되어야하고, 플레이어가 시작하는 스타트 포인트도 존재해야 해.
> * 몬스터는 항상 생성 포인트 주변에서 생성되며, 현재 만들어진 몬스터 이동 패턴을 활용해서 동작하기를 바래.
> * 시뮬레이션을 시각적인 정보로 미리 만들어질 맵의 구조를 보고 결정할 수 있는 형태면 좋겠어.
> * 사용할 맵 리소스를 자유롭게 추가할 수 있도록 리소스를 등록하는 형태의 기능이 있으면 좋겠어.
> * 게임에 등장하는 발판(충돌 영역 포함)의 배치 등이 플레이어가 점프로 이동 가능한 높이나 거리에 맞게 설계되면 좋겠어. (플랫포머 게임의 자연스러운 레벨디자인)
> * 바닥의 길은 사용자가 일일이 수정하지 않아도 충돌 영역이 자연스럽게 적용되면 좋겠어."

### 1-2. PD 추가 결정 4건 (PM 사전 명세 안건 답변)

| # | 안건 | PD 결정 | 본 설계 반영 |
|---|------|---------|------------|
| 1 | 시각 시뮬레이터 | Unity Editor Window 1순위 · 어려우면 Scene 미리보기 fallback | §10 — EditorWindow 채택 (실측 결과 1순위 가능) |
| 2 | 맵 리소스 단위 | 양쪽 다 — 배경=Tile, 일부 기믹=Prefab | §5 — Tile/Prefab 이원 분리 |
| 3 | 랜덤성 | 매번 완전 랜덤 (Seed 입력 X) | §4 — `UnityEngine.Random` 매 생성 새 시드 |
| 4 | 거점 밀도 | 맵 길이·비율을 옵션으로 미리 지정 → 옵션 값 동작 | §8 — `MapGenerationConfig.SpawnDensity 0.0~1.0` |

### 1-3. 헌법 제1원칙 정합

- **①** AI 에이전트 활용 — 개발팀장 Opus 설계, 클라이언트팀 Sonnet 구현 (C49)
- **②** 경험 축적 — BT5-Dev 발판 시스템·BT12 스킬 시스템 자산 전부 계승
- **③** 허위 보고 금지 — §2 사전 실측 영역에서 모든 추정 X·Read 결과만 기재
- **④** 조직 단위 운영 — 본 단계 Phase 1 단독·Phase 2 분할 권고는 §13
- **⑤** 세션 연속성 — 본 설계서 = 단일 SOT, Phase 2 어떤 세션에서 진입해도 정합

---

## §2. 사전 실측 결과 (C39-10 의무 — 위반 #1~#7 재발 차단)

### 2-1. KinematicObject (`E:/EerieVillage/Assets/Scripts/Mechanics/KinematicObject.cs`)

| 항목 | 실측값 | 본 설계 활용 |
|------|--------|------------|
| `minGroundNormalY` | 0.65f | 발판 경사 한계 — 본 설계 평탄 발판 고정 |
| `gravityModifier` | public 1f (Player/Enemy prefab) | 점프 물리 식 계산 입력값 |
| `Bounce(float)` | velocity.y 직접 set | 본 설계 미사용 |
| `Teleport(Vector3)` | body.position + velocity *=0 | 본 설계 Player Spawn 시 활용 |
| `body.Cast(contactFilter)` | hitBuffer[16] · contactFilter Layer mask 적용 | Drop-Through 발판 통과 메커니즘 SOT |
| `velocity.x = targetVelocity.x` | FixedUpdate 매 frame 덮어씀 | 직접 set 1 frame만 효과 |
| `ignoreCollisions` (2026-05-15) | 날으는 몬스터용·body.Cast skip | 본 설계 무관 (M002 박쥐 영역) |

### 2-2. PlayerController (`E:/EerieVillage/Assets/Scripts/Mechanics/PlayerController.cs`)

| 항목 | 실측값 |
|------|--------|
| `maxSpeed` | 3 (Player.prefab line 63) — BT12 Phase 2-D 영역 7→3 정정 |
| `jumpTakeOffSpeed` | 7 (Player.prefab line 64) |
| `JUMP_THROUGH_LAYER` | const 16 |
| `JUMP_ASCENT_DURATION` | const 0.4f (점프 ascending mask OFF) |
| `DROP_THROUGH_DURATION` | const 0.3f (Down+Jump Drop-Through mask OFF) |
| `UpdateContactFilterForDropThrough` | footHit Raycast 3점(좌·중·우)·nearApex+horizontalIntent 영역 강제 Drop-Through |
| `Awake()` 자동 부착 | PlayerInvulnerabilityFlash·ResurrectPromptUI·PlayerProgression·PlayerSkillInventory·SkillInventoryHUD |

### 2-3. PlatformerModel (`E:/EerieVillage/Assets/Scripts/Model/PlatformerModel.cs`)

| 항목 | 실측값 |
|------|--------|
| `jumpModifier` | 1.5f |
| `jumpDeceleration` | 0.5f (release 시 velocity.y × 0.5) |

### 2-4. Physics2D (`E:/EerieVillage/ProjectSettings/Physics2DSettings.asset`)

| 항목 | 실측값 |
|------|--------|
| `m_Gravity.y` | -9.81f |
| `m_VelocityIterations / PositionIterations` | 8 / 3 |

### 2-5. 점프 도달 한계 산출 (실측 수치 기반)

PlayerController.ComputeVelocity 영역:
```
velocity.y = jumpTakeOffSpeed × model.jumpModifier = 7 × 1.5 = 10.5 m/s
```

KinematicObject.FixedUpdate 영역 gravity 적용:
```
velocity.y -= 9.81 × gravityModifier × dt = 9.81 × 1 × dt
```

| 산출 | 공식 | 값 |
|------|------|---|
| 최대 점프 높이 (full hold) | H_max = v0² / (2g) = 10.5² / (2 × 9.81) | **5.62m** |
| 점프 정점 도달 시간 | t_up = v0 / g = 10.5 / 9.81 | **1.07s** |
| 점프 왕복 시간 (착지) | t_round = 2 × t_up | **2.14s** |
| 점프 중 수평 최대 거리 | D_max = maxSpeed × t_round = 3 × 2.14 | **6.42m** |
| Stop Jump (release) 시 ascending 절반 cap | v_after = 0.5 × v_now → H_partial ≈ H_max × 0.25 | **~1.4m (조기 release 영역)** |
| 안전 점프 거리 (90% margin) | D_max × 0.9 | **~5.78m** |
| 안전 점프 높이 (90% margin) | H_max × 0.9 | **~5.06m** |

**본 설계 발판 배치 룰 (§6)에 이 수치 직접 적용**.

### 2-6. EnemyController (`E:/EerieVillage/Assets/Scripts/Mechanics/EnemyController.cs`)

| 항목 | 실측값 | 본 설계 활용 |
|------|--------|------------|
| `patrolMinRange / patrolMaxRange` | 5 / 10 (Enemy.prefab) — 본 EC.cs default 50/75는 prefab override됨 | Spawn Point 영역 좌우 ±10m patrol 영역 확보 의무 |
| `cliffCheckDistance / cliffCheckDepth` | 1.0 / 2.0 | 발판 끝단 cliff 검출 — Spawn 발판 폭 >2m 필수 |
| `groundLayerMask` | (1<<0) \| (1<<16) — Layer 0 Level + Layer 16 AutoForeground | Spawn 발판 Layer 0 또는 16 |
| `fallThreshold` | 1.0f → startY - 1.0 미만 시 시작 위치 텔레포트 | 떨어진 Enemy 자동 복귀 (BT102) |
| `IsFlying` | controller name "M002" 매칭 시 true · cliffCheck skip | 본 설계 M002는 공중 spawn |
| `_isInitialized` | Start 시점 MeasureSafeWalkDistance 완료 후 true | Enemy spawn은 Scene 로드 후 1 frame 대기 의무 |
| `MeasureSafeWalkDistance` | Tilemap WorldToCell + HasTile cell 연속 + 위·아래 1 cell 인접 (계단 정합) | Spawn 발판 Tilemap 등록 필수 |
| `_cachedPlayer` collide | distance < 1.5f or VisualBounds.Intersects → Decrement (i-frame 외) | 본 설계 무관 (Enemy 동작 SOT) |

### 2-7. GameOptimizer (`E:/EerieVillage/Assets/Scripts/Mechanics/GameOptimizer.cs`) — 본 설계 결정적 영향

| 동작 | 본 설계 영향 |
|------|-------------|
| `Init()` BeforeSceneLoad — targetFrameRate 60·Physics2D.IgnoreLayerCollision(13,14) | 본 설계 무관 (이미 적용) |
| `SetupJumpThroughPlatforms()` AfterSceneLoad — 모든 Collider2D 분류 | **본 설계 절차 생성 Tilemap은 본 hook 진입 전 완료 의무** |
| Level Tilemap(name="Level") = Layer 0 보존 | 본 설계 지면 Tilemap 영역 name="Level" 강제 |
| 그 외 비-trigger Collider2D = Layer 16 강제 | 본 설계 공중 발판 Tilemap name≠"Level" 시 자동 Layer 16 |
| `Foreground` GameObject = 가림막 시각만·TilemapCollider 제거 | 본 설계 절차 생성 시 "Foreground" name 회피 |
| `AutoForeground` 자동 생성·Grid 자식 | 본 설계는 Level/AutoForeground 직접 생성 OR GameOptimizer 자동 분류 위임 (§7 결정) |
| Level Tile worldY ≥ playerY+1.5 OR 작은 공중 발판(가로≤8+위아래 빈) → AutoForeground 이동·Sprite 강제 | **본 설계 = 두 옵션** (A) 절차 생성기에서 즉시 분리 (B) Level 단일 Tilemap 배치 후 GameOptimizer 위임 |
| TileGround prefix 제외 | 본 설계 지면 Tile name "TileGround*" 강제 영역 |

### 2-8. Tile 자산 (`E:/EerieVillage/Assets/Tiles/`)

| Tile | m_ColliderType | 용도 |
|------|---------------|------|
| TileGround.asset | 1 (Sprite) | 지면 |
| TileGroundTop.asset | 1 | 지면 표면 |
| TileGroundDark.asset | 1 | 어두운 지면 변형 |
| TileFloatingLeftEdge.asset | 1 | 공중 발판 좌측 끝 |
| TileFloatingTileMiddle.asset | 1 | 공중 발판 중간 |
| TileFloatingRightEdge.asset | 1 | 공중 발판 우측 끝 |
| cloud.asset | 1 | 공중 발판 변형 (구름) |
| fence.asset | (확인 보류 — 기획팀 장식 영역) | 배경 |
| ShortBuilding/TallBuilding/MidgroundFiller.asset | (배경) | 배경 영역 |

**전 Tile m_ColliderType=1(Sprite) 사전 확정** → 본 설계 §7 자동 충돌 적용 핵심 근거.

### 2-9. Prefab (`E:/EerieVillage/Assets/Prefabs/`)

| Prefab | m_Layer | 본 설계 활용 |
|--------|---------|------------|
| Player.prefab | 13 | 1 인스턴스 (Start Point Spawn) |
| Enemy.prefab | 14 | (구) MonsterRandomizer 영역 random 6종 |
| Enemy_M001_Wolf~M006_Spider.prefab | 14 (추정) | 본 설계 SpawnPoint별 개별 prefab 선택 OR Enemy.prefab + MonsterRandomizer 위임 (§9) |

### 2-10. Scene·기획 정합 (`프로젝트/EerieVillage/기획/level/01_스테이지_구조.md` v0.1)

| 기획 영역 | 본 설계 정합 |
|---------|------------|
| 5종 스테이지 (마을 ↔ 1-5) | 본 설계 절차 생성기는 **스테이지별 옵션 카탈로그** 제공 (§11) |
| 3단 구성 (도입·전개·보스 방) | 본 설계 §4 청크 분할 권고: Intro/Mid/BossPre 3 영역 + Boss 방은 별도 수동 Scene |
| 횡스크롤 길이 5~10분 | 본 설계 맵 길이 옵션 = Tile 단위 길이 (§15 PD 결정 안건) |
| **§5 자동 생성 vs 수동 배치 — "전체 수동 배치 + 구간 2 적 배치 소규모 셔플" 채택** | **PD 신규 지시(2026-05-18) = 본 기획 결정 OVERRIDE — "맵의 길이만 지정하면, 랜덤으로 자동 생성"** → 기획팀 후속 정합 안건 (§15) |
| §10 §10-2 "낙하 함정" 등 지형 기믹 — 개발팀 C11 판단 이관 | 본 설계 §11 MapResourceCatalog → 기믹 Prefab 등록 영역 |

### 2-11. BT12 스킬 시스템 영향 평가 (`프로젝트/EerieVillage/개발/spec/스킬_시스템_설계_v1.md` v1.0)

| BT12 영역 | 본 설계 영향 |
|----------|------------|
| PlayerSkillInventory·ActiveSkillRuntime·6종 Effector(Lightning·Melee·Spirit·Poison·Laser·Projectile) | **영향 없음** — 본 설계는 맵 절차 생성만, Player 컴포넌트는 PlayerController.Awake 자동 부착 그대로 |
| 스킬 효과 충돌 영역 (Effector → Health.Decrement) | 본 설계 영향 없음 — Enemy.prefab Health 그대로 |
| Enemy ATK/HP | 본 설계 영향 없음 — Enemy.prefab 그대로 spawn |
| MonsterRandomizer (6 controller random) | 본 설계 § 9 Spawn 영역에서 활용 가능 (옵션 1: Enemy.prefab spawn → Awake에서 Random / 옵션 2: 본 설계 SpawnPoint config에서 특정 M00N 강제) |

### 2-12. 기존 MapGenerator/Spawn 시스템 0건 사전 확정

`grep MapGenerator|RandomMap|ProceduralMap|MapBuilder E:/EerieVillage/Assets/Scripts` → 0건.
`grep SpawnPoint` → 1건 (`Mechanics/SpawnPoint.cs` = 11줄 빈 marker 클래스).

→ 본 설계 = **완전 신규 시스템 도입**. 기존 코드와 충돌 없음. GameOptimizer·EnemyController·PlayerController 영역과 인터페이스 정합만 의무.

### 2-13. Editor 폴더 영역

`E:/EerieVillage/Assets/Editor/` 영역 GitAutoSync만 존재 → **EditorWindow 신규 도입 깨끗** (Phase 2-C 영역).

---

## §3. 아키텍처 4계층

```
┌────────────────────────────────────────────────────────────────┐
│ 계층 4: 시각 시뮬레이터 (Editor only)                            │
│   - MapPreviewWindow (EditorWindow)                            │
│     · GUI 영역 옵션 입력 + Generate 버튼 + 미리보기 캔버스       │
│     · Handles.DrawLine·DrawSolidArc → 발판·SpawnPoint·점프 영역 │
│   - MapSceneApplier (Scene 적용 버튼) — Phase 2-C               │
└────────────────────────────────────────────────────────────────┘
                              ▲ 호출
┌────────────────────────────────────────────────────────────────┐
│ 계층 3: 절차 생성기 (Runtime + Editor 양립)                       │
│   - MapGenerator (정적 메서드 또는 MonoBehaviour)                │
│     · Input: MapGenerationConfig (옵션 SO 또는 struct)          │
│     · Output: MapBlueprint (in-memory 데이터)                   │
│   - PlatformLayoutPlanner (§6 발판 배치 룰)                      │
│   - SpawnPointPlanner (§8 거점 배치)                             │
│   - TerrainPlanner (§7 바닥 자동 충돌)                            │
└────────────────────────────────────────────────────────────────┘
                              ▲ 참조
┌────────────────────────────────────────────────────────────────┐
│ 계층 2: 데이터 SOT (ScriptableObject)                           │
│   - MapResourceCatalog (Tile·Prefab 카탈로그)                   │
│   - MapGenerationConfig (옵션·길이·밀도)                        │
│   - PlatformerJumpProfile (실측 점프 수치 SOT)                   │
│   - SpawnRuleProfile (Enemy spawn 규칙)                         │
└────────────────────────────────────────────────────────────────┘
                              ▲ 입력
┌────────────────────────────────────────────────────────────────┐
│ 계층 1: 실행·런타임 적용                                          │
│   - MapApplier (Scene 적용 — Tilemap.SetTile + Instantiate)     │
│   - Player Start 배치 + Enemy SpawnPoint 인스턴스 생성           │
│   - GameOptimizer.SetupJumpThroughPlatforms() 진입 전 완료 의무  │
└────────────────────────────────────────────────────────────────┘
```

### 3-1. 데이터 흐름

```
[PD]                                    [본 시스템]
 │                                          │
 ├─ MapResourceCatalog SO 영역 등록 ───────►│ Tile·Prefab 풀
 │  (Editor·자유 추가)                       │
 │                                          │
 ├─ MapGenerationConfig 옵션 지정 ─────────►│
 │  · LengthTiles=120                       │
 │  · SpawnDensity=0.5                      │
 │  · StageProfile=Stage1                   │
 │                                          ▼
 │                            [MapGenerator.Generate(config)]
 │                                          │
 │                                          ▼
 │                                  [MapBlueprint]
 │                                  · TerrainTiles[]
 │                                  · PlatformTiles[]
 │                                  · SpawnPoints[]
 │                                  · StartPoint
 │                                  · Goal
 │                                          │
 ├─ MapPreviewWindow [Generate] ────────────┤ in-memory preview
 │  (시각 검토) ◄───── DrawHandles ──────────┤
 │                                          │
 ├─ [Apply to Scene] ──────────────────────►│ MapApplier
 │                                          │ · Tilemap.SetTile
 │                                          │ · Instantiate(Enemy/Player)
 │                                          ▼
 │                                  [Scene 영역 완성]
 │                                          │
 │                                          ▼
 │                         (Play) GameOptimizer.AfterSceneLoad
 │                                          │
 │                                          ▼
 │                            EnemyController.Start → patrol
```

---

## §4. 절차 생성 알고리즘

### 4-1. 입력

```csharp
[CreateAssetMenu(menuName = "EerieVillage/Map/Generation Config")]
public class MapGenerationConfig : ScriptableObject
{
    [Header("길이 영역")]
    public int lengthTiles = 120;       // 가로 Tile 총 길이 (1 Tile = 1 Unity unit)

    [Header("거점·밀도 영역")]
    [Range(0f, 1f)] public float spawnDensity = 0.4f;
    public int minSpawnSpacingTiles = 12;
    public int maxSpawnPerMap = 16;

    [Header("발판 영역")]
    [Range(0f, 1f)] public float platformDensity = 0.5f;
    public int minPlatformGapTiles = 3;
    public int maxPlatformGapTiles = 6;
    public int minPlatformWidthTiles = 2;
    public int maxPlatformWidthTiles = 6;
    public int minPlatformHeight = 2;   // 지면 위 Tile 단위
    public int maxPlatformHeight = 5;   // 점프 도달 5.06m 영역 (§2-5)

    [Header("Start/Goal 영역")]
    public int startMarginTiles = 4;    // 좌측 끝 Start Point margin
    public int goalMarginTiles = 4;     // 우측 끝 Goal margin

    [Header("리소스 영역")]
    public MapResourceCatalog catalog;
    public PlatformerJumpProfile jumpProfile;
    public StageThemeProfile stageTheme;  // 시각 테마 (Tile 가중치)
}
```

### 4-2. 출력 (in-memory)

```csharp
public class MapBlueprint
{
    public int lengthTiles;
    public int height;                          // 청크 영역 최대 높이
    public List<TerrainTilePlace> terrain;      // 지면 Tile (Layer 0)
    public List<PlatformTilePlace> platforms;   // 공중 발판 Tile (Layer 16)
    public List<SpawnPointPlace> spawnPoints;   // Enemy spawn 거점
    public Vector2 startPoint;                  // Player 시작 좌표
    public Vector2 goalPoint;                   // VictoryZone 좌표
    public List<PropPlace> backgroundProps;     // fence·building 등 배경 Prefab
    public List<GimmickPlace> gimmicks;         // 함정·이동 발판 등 (§11)
    public int generationSeed;                  // 추적용 (PD 결정 3: 매번 랜덤이나 디버그 추적)
}
```

### 4-3. 절차 (의사 코드)

```
Generate(config):
    1. seed = Random.Range(int.MinValue, int.MaxValue)
       Random.InitState(seed)   // PD 결정 3: 매번 완전 랜덤 (사용자 입력 X, 디버그용 기록만)

    2. lengthTiles = config.lengthTiles

    3. ── 청크 분할 ──
       chunks = SplitIntoChunks(lengthTiles, chunkSize=20)
       각 chunk 영역 Theme 변동 권고 (§4-4)

    4. ── 지면 생성 (TerrainPlanner) ──
       terrain = []
       baseY = 0
       for x in 0..lengthTiles:
           // 자연스러운 고저차 — Perlin noise 변조 (±2 tile)
           y = baseY + RoundedPerlin(x * 0.1f, amplitude=2)
           terrain.Add( {x, y, TileGround} )
           // 표면 1 Tile = TileGroundTop
           terrain.Add( {x, y+1, TileGroundTop} ) — y+1 미사용 (충돌 영역 영역)

    5. ── 공중 발판 생성 (PlatformLayoutPlanner) ──
       platforms = []
       x = config.startMarginTiles + 5
       while x < lengthTiles - config.goalMarginTiles:
           if Random.value < config.platformDensity:
               width = Random.Range(config.minPlatformWidthTiles, config.maxPlatformWidthTiles+1)
               height = Random.Range(config.minPlatformHeight, config.maxPlatformHeight+1)
               // 점프 도달 가능성 검증 (jumpProfile)
               canReach = ValidateJumpReach(prevPlatformOrGround, {x, terrainY+height, width})
               if canReach:
                   platforms.AddRange(GeneratePlatformTiles(x, terrainY+height, width))
                   x += width + Random.Range(config.minPlatformGapTiles, config.maxPlatformGapTiles+1)
                   continue
           x += 1

    6. ── 거점 생성 (SpawnPointPlanner) ──
       targetCount = floor(lengthTiles * config.spawnDensity / config.minSpawnSpacingTiles)
       targetCount = min(targetCount, config.maxSpawnPerMap)
       spawnPoints = []
       // 거점 = 지면 + 일부 공중 발판 (PD 결정 안건 §15)
       candidates = AllValidSpawnPositions(terrain, platforms, marginFromStart=8, marginFromGoal=8, minSpacing=config.minSpawnSpacingTiles)
       Shuffle(candidates)
       spawnPoints = candidates.Take(targetCount)
       각 SpawnPoint 영역 EnemyType = RandomFromCatalog(config.catalog.enemyPrefabs)

    7. ── Start/Goal ──
       startPoint = (config.startMarginTiles, terrain[startMarginTiles].y + 1.5)
       goalPoint = (lengthTiles - config.goalMarginTiles, terrain[lengthTiles-goalMarginTiles].y + 1.5)

    8. ── 배경·기믹 ──
       backgroundProps = ScatterBackgroundProps(terrain, config.catalog.backgroundPrefabs, density)
       gimmicks = ApplyGimmickRules(config.catalog.gimmickPrefabs, terrain, platforms, density)

    9. return MapBlueprint { ... }
```

### 4-4. 청크 분할 권고 (단조 영역 회피)

- chunkSize = 20 Tile 권고
- 각 chunk 영역 platformDensity 가중치 변동 (Intro=0.3, Mid=0.6, BossPre=0.4)
- stageTheme.tileVariantWeights에 따라 chunk별 시각 Tile pool 변동
- PD 결정 안건 §15-3 — chunk 영역 자체 옵션 (스테이지 진행 양상) 노출 영역

### 4-5. ValidateJumpReach (§6 정합)

```csharp
bool ValidateJumpReach(PlatformAnchor from, PlatformAnchor to)
{
    float dx = Mathf.Abs(to.x - from.x);
    float dy = to.y - from.y;  // 위로 이동 = 양수

    // 점프 도달 가능 영역: §2-5 실측
    float maxHorizontal = jumpProfile.safeHorizontalDistance;  // 5.78m (90% margin)
    float maxVertical = jumpProfile.safeVerticalHeight;        // 5.06m

    // 수평 거리 cap
    if (dx > maxHorizontal) return false;

    // 위로 점프: 높이 cap
    if (dy > maxVertical) return false;

    // 발판 → 발판: 포물선 통과 가능성 (단순 보수 — 최대 높이까지 도달 가능 시 OK)
    // dy 음수(아래) 발판 = 항상 도달 가능
    return true;
}
```

---

## §5. Tile (배경) vs Prefab (기믹) 분리 룰

PD 결정 2 — **양쪽 다** 활용.

### 5-1. Tile 영역 (Tilemap)

| 영역 | Tile 자산 | Layer | 사용 |
|------|---------|-------|------|
| 지면 (Level Tilemap) | TileGround·TileGroundTop·TileGroundDark | 0 | 무한 지면·고저차 |
| 공중 발판 (AutoForeground Tilemap) | TileFloatingLeftEdge·TileFloatingTileMiddle·TileFloatingRightEdge·cloud | 16 | Drop-Through 발판 |
| 배경 장식 (BackgroundTilemap) | fence·MidgroundFiller (선택) | (선택·collider X 또는 별도 Layer) | 시각 |

### 5-2. Prefab 영역 (Instantiate)

| 영역 | Prefab | 사용 |
|------|--------|------|
| Player | Player.prefab | Start Point 1 인스턴스 |
| Enemy | Enemy.prefab 또는 Enemy_M00N_*.prefab | SpawnPoint 영역 인스턴스 |
| 배경 건물 | ShortBuilding·TallBuilding | 배경 배치 (Prefab 또는 Tile both 옵션) |
| 기믹 | (신규) MovingPlatform·Trap·CheckPoint·VictoryZone 등 | 옵션 등록 영역 (§11) |

### 5-3. 결정 룰

```
요소 = 동적 동작·복잡 컴포넌트 영역? → Prefab
요소 = 정적 시각·단순 충돌·반복 배치? → Tile
경계 영역(건물 등) → 옵션·MapResourceCatalog에서 PD 자유 선택
```

---

## §6. 발판 배치 룰 (자연스러운 레벨 디자인)

### 6-1. PlatformerJumpProfile (실측 SOT)

```csharp
[CreateAssetMenu(menuName = "EerieVillage/Map/Jump Profile")]
public class PlatformerJumpProfile : ScriptableObject
{
    [Header("실측 (KinematicObject·PlayerController·PlatformerModel)")]
    public float jumpTakeOffSpeed = 7f;
    public float jumpModifier = 1.5f;
    public float gravity = 9.81f;
    public float maxSpeed = 3f;
    public float gravityModifier = 1f;

    // 산출 (자동 계산 — OnValidate)
    public float v0 => jumpTakeOffSpeed * jumpModifier;
    public float effectiveGravity => gravity * gravityModifier;
    public float maxVerticalHeight => (v0 * v0) / (2f * effectiveGravity);  // 5.62m
    public float jumpRoundTripTime => 2f * v0 / effectiveGravity;            // 2.14s
    public float maxHorizontalDistance => maxSpeed * jumpRoundTripTime;      // 6.42m

    [Header("안전 margin (자연 레벨 디자인)")]
    [Range(0.5f, 1.0f)] public float safetyMargin = 0.9f;
    public float safeVerticalHeight => maxVerticalHeight * safetyMargin;      // 5.06m
    public float safeHorizontalDistance => maxHorizontalDistance * safetyMargin;  // 5.78m
}
```

### 6-2. 배치 룰 (자연스러움)

| 룰 | 수치 | 근거 |
|----|-----|------|
| 발판 간 수평 간격 min | 3 Tile (3m) | 1 Tile 점프 가능·연속 점프 리듬 |
| 발판 간 수평 간격 max | 6 Tile (6m) | safeHorizontalDistance 5.78m 영역 안 |
| 발판 높이 min | 2 Tile (지면 위 2m) | Player 캡슐 높이 약 1m → 점프 의무 영역 |
| 발판 높이 max | 5 Tile (지면 위 5m) | safeVerticalHeight 5.06m 영역 안 |
| 발판 폭 min | 2 Tile (2m) | EnemyController cliffCheckDistance=1m 영역 안전 patrol |
| 발판 폭 max | 6 Tile (6m) | 단조 영역 회피 |
| 연속 점프 (계단식) 영역 dx | 3~4 Tile, dy +1~+2 Tile | 자연 상승 리듬 |
| 사선·이중 점프 | **PD 결정 안건 §15-2** | 현 KinematicObject 영역 2단 점프 미구현 |

### 6-3. 검증 영역

`MapGenerator.PlatformLayoutPlanner`가 모든 발판 영역 `PlatformerJumpProfile.ValidateJumpReach()` 통과 검증. 미통과 시 해당 발판 폐기 후 위치·높이 재시도 (최대 5회 retry).

### 6-4. EnemyController patrol 영역 (§9 정합)

발판 폭 ≥ 2 Tile + EnemyController fallThreshold=1m·cliffCheckDistance=1m → spawn 발판 영역 자동 patrol 동작 보장.

---

## §7. 바닥 자동 충돌 적용

### 7-1. PD 요구 "바닥의 길은 사용자가 일일이 수정하지 않아도 충돌 영역이 자연스럽게 적용"

### 7-2. 기존 자산 활용 (§2-8 실측)

- 모든 Tile 자산 m_ColliderType=1 (Sprite) 이미 강제
- 본 설계 = Tilemap 영역 SetTile 시 자동 영역 collider 적용

### 7-3. Tilemap 자동 충돌 영역 표준 패턴

```csharp
// MapApplier.cs
void ApplyTerrainToLevelTilemap(MapBlueprint blueprint)
{
    Tilemap levelTilemap = FindOrCreateLevelTilemap();
    foreach (var place in blueprint.terrain)
    {
        levelTilemap.SetTile(new Vector3Int(place.x, place.y, 0), place.tile);
        levelTilemap.SetColliderType(new Vector3Int(place.x, place.y, 0),
                                       Tile.ColliderType.Sprite);
    }
    var tc = levelTilemap.GetComponent<TilemapCollider2D>();
    if (tc == null) tc = levelTilemap.gameObject.AddComponent<TilemapCollider2D>();
    tc.compositeOperation = Collider2D.CompositeOperation.Merge;

    // CompositeCollider2D 통합 (지면 영역 단일 collider — 성능·정합)
    var cc = levelTilemap.GetComponent<CompositeCollider2D>();
    if (cc == null) cc = levelTilemap.gameObject.AddComponent<CompositeCollider2D>();
    var rb = levelTilemap.GetComponent<Rigidbody2D>();
    if (rb == null) rb = levelTilemap.gameObject.AddComponent<Rigidbody2D>();
    rb.bodyType = RigidbodyType2D.Static;

    tc.ProcessTilemapChanges();
    levelTilemap.gameObject.name = "Level";          // §2-7 GameOptimizer 영역 보존
    levelTilemap.gameObject.layer = 0;               // §2-7 GameOptimizer 영역 보존
}
```

### 7-4. 공중 발판 Tilemap 영역 (Drop-Through 영역)

```csharp
void ApplyPlatformsToAutoForegroundTilemap(MapBlueprint blueprint)
{
    Tilemap fgTilemap = FindOrCreateAutoForegroundTilemap();
    foreach (var place in blueprint.platforms)
    {
        fgTilemap.SetTile(new Vector3Int(place.x, place.y, 0), place.tile);
        fgTilemap.SetColliderType(new Vector3Int(place.x, place.y, 0),
                                    Tile.ColliderType.Sprite);
    }
    // GameOptimizer 영역 자동 영역 Layer 16 + TilemapCollider 보장
    fgTilemap.gameObject.name = "AutoForeground";
    fgTilemap.gameObject.layer = 16;
    EnsureCollider(fgTilemap);
}
```

### 7-5. 두 옵션 (PD 결정 안건 §15-4)

**옵션 A (권고)**: 본 설계 MapApplier 영역 직접 Level/AutoForeground 분리 적용 → GameOptimizer 영역 자동 분류 의존 X (예측성·결정성↑)

**옵션 B**: 모두 Level Tilemap에 배치 → GameOptimizer.SetupJumpThroughPlatforms() 영역 자동 분류 위임 (기존 시스템 활용·중복 회피)

→ **§15-4 PD 결정**.

본 PM 권고 옵션 A — Drop-Through 패턴 28회 시도(BT22~BT47)의 교훈 영역 명시적 분리가 안정.

### 7-6. Composite·자연스러운 충돌

CompositeCollider2D + Merge → 인접 Tile 영역 단일 collider 영역 통합. 가장자리 jitter·작은 틈 영역 차단.

---

## §8. 거점(생성 포인트) + Start Point 배치

### 8-1. 옵션 정합 (PD 결정 4)

```csharp
[Range(0f, 1f)] public float spawnDensity = 0.4f;
// 0.0 = 거점 0개 / 0.5 = 중간 밀도 / 1.0 = 최대 밀도 (maxSpawnPerMap cap)
public int minSpawnSpacingTiles = 12;
public int maxSpawnPerMap = 16;
```

### 8-2. 산출 공식

```
desiredCount = floor(lengthTiles * spawnDensity / minSpawnSpacingTiles)
finalCount = min(desiredCount, maxSpawnPerMap)
```

예: lengthTiles=120·spawnDensity=0.5·minSpawnSpacingTiles=12 → desiredCount=5·finalCount=5

### 8-3. 거점 후보 영역

| 영역 | 적격 |
|------|------|
| 지면 평탄 영역 (Tile 폭 ≥3 연속) | ✓ |
| 공중 발판 (폭 ≥3 Tile) | ✓ (PD 결정 안건 §15-5: 거점을 공중 발판에도 둘지) |
| Start Point ±8 Tile | ✗ (난이도 초기) |
| Goal ±8 Tile | ✗ |
| 다른 거점 < minSpawnSpacingTiles | ✗ |

### 8-4. Start Point

- 좌측 끝 + `startMarginTiles` (default 4) 위치
- 지면 Tile 표면 + 1.5m 위 (Player 캡슐 영역 안전 spawn)
- 본 설계 = 항상 좌측 고정 (PD 결정 안건 §15-6 — 양 끝 random 옵션)

### 8-5. Goal

- 우측 끝 - `goalMarginTiles` 위치
- VictoryZone Prefab Instantiate (현 영역 VictoryZone Prefab 영역 검증 필요 — `Mechanics/VictoryZone.cs` 존재 확인됨, §2-7 GameOptimizer 영역 excluded)

### 8-6. SpawnPoint 영역 (MonoBehaviour 확장)

```csharp
public class SpawnPoint : MonoBehaviour  // 기존 11줄 영역 확장
{
    [Header("Spawn 영역")]
    public GameObject enemyPrefab;       // Enemy_M00N or Enemy.prefab + MonsterRandomizer
    public float spawnRadiusX = 0.5f;    // 거점 ±X Tile 영역 random spawn
    public int initialSpawnCount = 1;    // 최초 spawn 수
    public int maxConcurrent = 1;        // 동시 spawn 상한 (현 영역 1 — re-spawn 영역 PD 결정 §15-7)

    [Header("미리보기 영역 — Editor only")]
    public Color gizmoColor = new Color(1f, 0.3f, 0.3f, 0.5f);

    void Start()
    {
        for (int i = 0; i < initialSpawnCount; i++)
        {
            Vector3 pos = transform.position + new Vector3(
                Random.Range(-spawnRadiusX, spawnRadiusX), 0f, 0f);
            var enemy = Instantiate(enemyPrefab, pos, Quaternion.identity);
            // EnemyController.Awake → _startX·_startY 설정 자동 진행 (§2-6 정합)
        }
    }

#if UNITY_EDITOR
    void OnDrawGizmos()
    {
        Gizmos.color = gizmoColor;
        Gizmos.DrawWireSphere(transform.position, 0.5f);
        Gizmos.DrawLine(transform.position + Vector3.left * spawnRadiusX,
                        transform.position + Vector3.right * spawnRadiusX);
    }
#endif
}
```

---

## §9. 몬스터 spawn 룰

### 9-1. PD 요구 "현재 만들어진 몬스터 이동 패턴을 활용"

§2-6 EnemyController 자동 patrol 시스템 = **완전 계승**. SpawnPoint 영역 Instantiate만 처리.

### 9-2. Enemy spawn 절차

```
1. MapApplier가 SpawnPoint Prefab 영역 Scene에 배치 (위치=거점 좌표)
2. SpawnPoint.Start() → enemyPrefab Instantiate (Awake → MonsterRandomizer 영역 controller random)
3. EnemyController.Awake() → _startX = transform.position.x, _startY = transform.position.y
4. EnemyController.Start() → MeasureSafeWalkDistance(±1) — Tilemap cell 기반 좌우 patrol 거리 측정
5. _isInitialized = true → UpdatePatrol() 영역 자동 patrol 시작
6. 떨어짐 검출 (y < _startY - 1.0) → 시작 위치 텔레포트
```

### 9-3. EnemyType 카탈로그 (MapResourceCatalog 영역)

| Catalog 슬롯 | 영역 |
|------------|------|
| `enemyPrefabs[]` | Enemy_M001_Wolf·M002_Bat·M003_ZombieM·M004_ZombieF·M005_Ghost·M006_Spider |
| `enemyWeights[]` | stageTheme별 가중치 (Stage1=Wolf 70%·Bat 30% 등) |
| `flyingEnemyPrefabs[]` | M002 Bat (cliffCheck skip·공중 spawn) |

### 9-4. spawn 위치 영역 — 거점 주변

```
spawnPos = spawnPoint.position + Vector3(Random(-spawnRadiusX, spawnRadiusX), 0, 0)
```

EnemyController _startX는 Awake 시점 transform.position.x → 본 spawn 위치 = patrol 기준점.

### 9-5. 동시 spawn 상한

현 영역 SpawnPoint 1개당 1 Enemy. **재-spawn (사망 후 시간 경과 재출현) 영역 = PD 결정 안건 §15-7**.

### 9-6. 공중 SpawnPoint (M002 Bat)

`IsFlying=true` 영역 EnemyController는 `flyingYOffset = 2f` 자동 적용 → SpawnPoint 영역 지면 위 SpawnPoint에 M002 prefab 배치해도 Awake 영역 +2m 자동 상승. 별도 영역 처리 불요.

---

## §10. 시각 시뮬레이터 — Unity Editor Window 1순위 결정

### 10-1. 가능성 판단 (PD 결정 1 — 1순위 EditorWindow)

**판단 결과: 가능. 1순위 EditorWindow 채택.**

근거:
- `E:/EerieVillage/Assets/Editor/` 영역 GitAutoSync만 존재·신규 EditorWindow 도입 깨끗 (§2-13)
- Unity EditorWindow + GUILayout + Handles.Draw* 영역 표준 패턴
- Tile data·SpawnPoint 위치·점프 도달 영역 모두 in-memory MapBlueprint 영역 직접 그리기 가능 (Scene 적용 전)

### 10-2. MapPreviewWindow 구조 (Editor 폴더)

```csharp
// E:/EerieVillage/Assets/Editor/Map/MapPreviewWindow.cs (신규)
public class MapPreviewWindow : EditorWindow
{
    [MenuItem("EerieVillage/Map/Preview")]
    public static void Open() => GetWindow<MapPreviewWindow>("Map Preview");

    MapGenerationConfig config;
    MapBlueprint currentBlueprint;
    Vector2 previewScroll;
    float zoom = 1f;

    void OnGUI()
    {
        EditorGUILayout.LabelField("Map Preview", EditorStyles.boldLabel);
        config = (MapGenerationConfig)EditorGUILayout.ObjectField("Config", config,
                                       typeof(MapGenerationConfig), false);
        EditorGUILayout.BeginHorizontal();
        if (GUILayout.Button("Generate", GUILayout.Width(100)))
        {
            currentBlueprint = MapGenerator.Generate(config);
        }
        if (GUILayout.Button("Apply to Scene", GUILayout.Width(120)))
        {
            MapApplier.Apply(currentBlueprint, config);
        }
        if (GUILayout.Button("Clear Scene", GUILayout.Width(100)))
        {
            MapApplier.Clear();
        }
        EditorGUILayout.EndHorizontal();

        zoom = EditorGUILayout.Slider("Zoom", zoom, 0.2f, 3f);

        if (currentBlueprint != null)
        {
            DrawPreview(currentBlueprint);
        }
    }

    void DrawPreview(MapBlueprint bp)
    {
        // GUI.DrawTexture로 grid 배경
        // bp.terrain·bp.platforms·bp.spawnPoints·bp.startPoint·bp.goalPoint를
        // Rect로 변환·GUI.Box 영역 그림
        // 점프 도달 영역 = Handles.DrawSolidArc 또는 GUI line
        var rect = GUILayoutUtility.GetRect(position.width, position.height - 100);
        previewScroll = GUI.BeginScrollView(rect, previewScroll,
                                             new Rect(0, 0, bp.lengthTiles * 16 * zoom,
                                                              bp.height * 16 * zoom));
        // Terrain (회색)
        foreach (var t in bp.terrain)
            EditorGUI.DrawRect(TileToRect(t.x, t.y, zoom), new Color(0.4f, 0.3f, 0.2f));
        // Platforms (녹색)
        foreach (var p in bp.platforms)
            EditorGUI.DrawRect(TileToRect(p.x, p.y, zoom), new Color(0.3f, 0.7f, 0.3f));
        // SpawnPoints (빨강)
        foreach (var s in bp.spawnPoints)
            EditorGUI.DrawRect(WorldToRect(s.position, zoom, size: 0.6f),
                                 new Color(0.9f, 0.2f, 0.2f));
        // Start (파랑)
        EditorGUI.DrawRect(WorldToRect(bp.startPoint, zoom, size: 1f),
                             new Color(0.2f, 0.5f, 0.9f));
        // Goal (노랑)
        EditorGUI.DrawRect(WorldToRect(bp.goalPoint, zoom, size: 1f),
                             new Color(0.9f, 0.9f, 0.2f));
        // 점프 도달 영역 (반투명 영역) — option toggle
        if (showJumpReach)
            DrawJumpReachOverlays(bp);
        GUI.EndScrollView();
    }
}
```

### 10-3. 시각 요소

| 요소 | 색상 | 표시 |
|------|------|------|
| 지면 Tile | 갈색 0.4·0.3·0.2 | 1×1 Rect |
| 공중 발판 Tile | 녹색 0.3·0.7·0.3 | 1×1 Rect |
| SpawnPoint | 빨강 0.9·0.2·0.2 | 0.6×0.6 Rect + 거점 ±spawnRadiusX 선 |
| Start Point | 파랑 0.2·0.5·0.9 | 1×1 Rect + "S" 텍스트 |
| Goal Point | 노랑 0.9·0.9·0.2 | 1×1 Rect + "G" 텍스트 |
| 점프 도달 영역 | 반투명 청 (toggle) | 발판 가장자리 영역 ±5.78m·+5.06m 호 |

### 10-4. Fallback — Scene 미리보기 (옵션 어렵 시)

본 PM 판단 = EditorWindow 영역 가능 — Fallback 비활성.
만약 GUILayout 영역 성능 한계 발생 시 → Scene Hierarchy 영역 Empty GameObject ("MapPreview_Temp") 영역 placeholder Sprite Instantiate → SceneView 영역 직접 표시.

### 10-5. Editor 호출 영역 워크플로우

```
1. PD: MapGenerationConfig SO Editor 영역 옵션 조정
2. PD: EerieVillage/Map/Preview 메뉴 → MapPreviewWindow 활성
3. PD: [Generate] → in-memory MapBlueprint 확인
4. PD: 시각 영역 만족 시 [Apply to Scene] → Tilemap·Prefab 적용
5. PD: 불만족 시 [Generate] 반복 (매번 완전 랜덤·PD 결정 3)
6. PD: Play → 실제 게임 확인
```

---

## §11. 맵 리소스 등록 시스템

### 11-1. MapResourceCatalog (ScriptableObject)

```csharp
[CreateAssetMenu(menuName = "EerieVillage/Map/Resource Catalog")]
public class MapResourceCatalog : ScriptableObject
{
    [Header("지면 Tile 영역")]
    public TileEntry[] groundTiles;       // TileGround·TileGroundTop·TileGroundDark

    [Header("공중 발판 Tile 영역")]
    public TileEntry[] platformLeftEdge;
    public TileEntry[] platformMiddle;
    public TileEntry[] platformRightEdge;
    public TileEntry[] platformCloud;     // 변형 (cloud.asset)

    [Header("배경 Tile 영역 (선택)")]
    public TileEntry[] backgroundTiles;   // fence·MidgroundFiller

    [Header("Enemy Prefab 영역")]
    public PrefabEntry[] enemyPrefabs;    // Enemy_M001~M006

    [Header("Player Prefab 영역")]
    public GameObject playerPrefab;

    [Header("Goal Prefab 영역")]
    public GameObject goalPrefab;          // VictoryZone

    [Header("배경 Prefab 영역")]
    public PrefabEntry[] backgroundPrefabs;  // ShortBuilding·TallBuilding 등

    [Header("기믹 Prefab 영역 (신규·향후 확장)")]
    public PrefabEntry[] gimmickPrefabs;     // 이동 발판·낙하 함정·체크포인트 등

    [Header("SpawnPoint Prefab 영역")]
    public GameObject spawnPointPrefab;     // SpawnPoint.cs 부착 영역
}

[System.Serializable]
public class TileEntry
{
    public TileBase tile;
    [Range(0f, 10f)] public float weight = 1f;
    public string[] tags;  // "stage1", "dark" 등
}

[System.Serializable]
public class PrefabEntry
{
    public GameObject prefab;
    [Range(0f, 10f)] public float weight = 1f;
    public string[] tags;
}
```

### 11-2. PD 자유 등록 영역

PD가 신규 Tile·Prefab 영역 영역 추가 시 → MapResourceCatalog asset 영역 Inspector 영역 슬롯 영역 드래그·드롭. 코드 수정 X·tag·weight 영역 분류만.

### 11-3. StageThemeProfile (스테이지별 Theme)

```csharp
[CreateAssetMenu(menuName = "EerieVillage/Map/Stage Theme")]
public class StageThemeProfile : ScriptableObject
{
    public string stageName = "Stage1_DuskVillageOutskirts";
    public MapResourceCatalog catalog;     // base catalog
    public string[] filterTags;            // "stage1" tag 영역 Tile만 활용
    public Color skyTint = Color.white;
    // 추후 — 음악·환경 영역
}
```

### 11-4. 기획 SOT 5종 스테이지 정합

`프로젝트/EerieVillage/기획/level/01_스테이지_구조.md` §2 영역 5종 stageTheme 영역 매핑:

| Stage | StageThemeProfile.stageName | filterTags |
|-------|---------------------------|------------|
| 1 황혼 고을 어귀 | Stage1_DuskVillageOutskirts | stage1·dusk |
| 2 버려진 산사 | Stage2_AbandonedMountainTemple | stage2·moonlit |
| 3 뼈 바위 계곡 | Stage3_BoneRockValley | stage3·foggy |
| 4 폐가·지하 창고 | Stage4_AbandonedHouseBasement | stage4·midnight |
| 5 공동묘지 심부 | Stage5_GraveyardDepths | stage5·dawn |

---

## §12. 기존 시스템 통합 영역

### 12-1. BT5-Dev 발판 시스템 영역

| 시스템 | 본 설계 활용 |
|--------|------------|
| KinematicObject contactFilter | Drop-Through Layer 16 발판 — 본 설계 AutoForeground Tilemap Layer 16 강제 정합 |
| PlayerController UpdateContactFilterForDropThrough | 본 설계 무관 (Player 영역 그대로) |
| GameOptimizer.SetupJumpThroughPlatforms | **상호작용 의무** — §7-5 옵션 A/B 결정 |
| Tile m_ColliderType=1 (Sprite) | §2-8 모든 Tile 기 강제 → 본 설계 SetColliderType(Sprite) 영역 정합 |
| CompositeCollider2D Merge | 본 설계 Level Tilemap 영역 부착 (§7-3) |

### 12-2. EnemyController patrol 영역

| 영역 | 본 설계 정합 |
|------|------------|
| _startX·_startY Awake 측정 | SpawnPoint.Instantiate → Enemy.Awake 자동 동작 |
| MeasureSafeWalkDistance Tilemap cell 기반 | 본 설계 = AutoForeground Tilemap 영역 등록 시 자동 patrol |
| cliffCheckDistance=1m | 본 설계 발판 폭 min 2 Tile 영역 안전 |
| fallThreshold=1m → 시작 위치 텔레포트 | 본 설계 spawn 발판 영역 EnemyController 자동 복귀 |
| IsFlying M002 Bat | 본 설계 catalog 영역 flyingEnemyPrefabs 영역 별도 분류 |

### 12-3. KinematicObject 점프 물리 영역

본 설계 = `PlatformerJumpProfile` SO 영역 실측 수치 직접 참조. KinematicObject 영역 변경 X.

### 12-4. M001~M006 6 Enemy prefab 영역

본 설계 = MapResourceCatalog 영역 6 prefab 등록·stageTheme별 가중치. MonsterRandomizer 영역 random 6종 활용 영역 = 옵션 (Enemy.prefab + MonsterRandomizer) vs (Enemy_M00N 직접 prefab) — **PD 결정 안건 §15-8**.

### 12-5. BT12 스킬 시스템 영역

**영향 없음** (§2-11). Player·Enemy 영역 그대로 spawn → PlayerSkillInventory·ActiveSkillRuntime·6 Effector 모두 정상 동작.

### 12-6. GitAutoSync (Editor)

EerieVillage `E:/EerieVillage/Assets/Editor/GitAutoSync/` 영역 영향 X (별도 영역).

---

## §13. Phase 2 작업 단위 분해 (C49 단계 정합)

### 13-1. Phase 2-A — 데이터 SOT 영역 (클라이언트팀 Sonnet)

| 산출물 | 위치 | 분량 |
|--------|------|------|
| PlatformerJumpProfile.cs (ScriptableObject) | `Assets/Scripts/Map/Data/` | 50줄 |
| MapResourceCatalog.cs + TileEntry/PrefabEntry | `Assets/Scripts/Map/Data/` | 80줄 |
| StageThemeProfile.cs | `Assets/Scripts/Map/Data/` | 30줄 |
| MapGenerationConfig.cs | `Assets/Scripts/Map/Data/` | 60줄 |
| MapBlueprint.cs + Place 영역 4종 struct | `Assets/Scripts/Map/Data/` | 100줄 |
| Default .asset 인스턴스 4종 (실측 수치 반영) | `Assets/Data/Map/` | (PD 미리 1개·확장 PD) |

### 13-2. Phase 2-B — 절차 생성기 (클라이언트팀 Sonnet)

| 산출물 | 분량 |
|--------|------|
| MapGenerator.cs (static·Generate 메서드) | 150줄 |
| TerrainPlanner.cs (Perlin 변조·고저차) | 80줄 |
| PlatformLayoutPlanner.cs (배치 룰·ValidateJumpReach) | 150줄 |
| SpawnPointPlanner.cs (밀도·간격·후보 영역) | 100줄 |
| BackgroundPropPlanner.cs (배경 산포) | 50줄 |
| EditMode 테스트 — 100회 Generate·ValidateJumpReach 모든 발판 통과 검증 | 200줄 |

### 13-3. Phase 2-C — Editor Window + Applier (클라이언트팀 Sonnet)

| 산출물 | 분량 |
|--------|------|
| MapPreviewWindow.cs (EditorWindow + DrawPreview) | 250줄 |
| MapApplier.cs (Tilemap.SetTile + Instantiate + Clear) | 150줄 |
| SpawnPoint.cs 확장 (Spawn 영역 옵션·OnDrawGizmos) | 80줄 |
| Editor 메뉴·shortcut | (포함) |

### 13-4. Phase 2-D — QA·정합 (개발팀장 Opus 검증)

| 영역 | 영역 |
|------|------|
| Stage1~5 Default Config 각 영역 Generate + Apply + Play 검증 | 5종 |
| 점프 도달 가능성 검증 — 100% pass | 정량 |
| Enemy patrol 정합 검증 (BT109 Tilemap cell 영역 정합) | 정량 |
| GameOptimizer SetupJumpThroughPlatforms 충돌 검증 | 정성 |
| 시뮬레이터 Apply→Play 일관성 검증 | 정성 |

### 13-5. C49 표준 프로세스 적용

1. **개발팀장 (Opus·본 문서)** 설계 v1 산출 ✓
2. **클라이언트팀장 (Opus)** 본 문서 영역 Phase 2-A/B/C 분할 위임 결정
3. **게임플레이 프로그래머 (Sonnet)** 코드 구현
4. **클라이언트팀장 (Opus)** Phase 2 코드 검증
5. **개발팀장 (Opus)** Phase 2-D 영역 정합 검증 → 완료 아카이브

---

## §14. 기각안

### 14-1. (기각 1) Seed 입력 기반 재현 가능 랜덤 채택

**기각 이유**: PD 결정 3 — "매번 완전 랜덤 (Seed 입력 X · 재현성 비요구)". Seed 입력 UI·재현 로직 영역 코드 부담 + PD 명시 비요구.

**보완**: MapBlueprint.generationSeed 영역 자동 기록 (디버그·로그 영역 추적용). PD가 후속 재현 의무 발생 시 generationSeed 영역 InitState 재 invoke 가능.

### 14-2. (기각 2) 전체 수동 배치 + 부분 셔플 (기존 기획 §5 채택)

**기각 이유**: PD 신규 지시 (2026-05-18) = "맵의 길이만 지정하면, 랜덤으로 자동 생성" → 기획 §5 "전체 수동 배치 + 구간 2 적 배치 소규모 셔플" **OVERRIDE**.

**기획 후속 정합 안건**: §15-1 PD 결정 대기. 기획팀 영역 SOT 정정 의무 (level-designer 후속).

### 14-3. (기각 3) Cellular Automata 또는 BSP 영역 던전 생성 알고리즘

**기각 이유**: 횡스크롤 플랫포머 영역 1D 진행 영역 단순 chunk 분할 (4-3) 영역 충분. CA·BSP 영역 2D top-down 영역 적합.

### 14-4. (기각 4) Runtime Generate (게임 진입 시 매번 절차 생성)

**기각 이유**: 본 설계 Phase 1 = Editor 영역 Generate→Apply→Save Scene. Runtime Generate 옵션은 향후 확장 (Phase 3-C 영역). 현 Phase = PD 시각 검토 영역 = Editor 영역 적용 후 Play.

**보완**: MapGenerator·MapApplier 영역 Editor·Runtime 양립 가능 영역 설계 → Phase 3-C 영역 즉시 활성 가능.

### 14-5. (기각 5) Procedural 영역 별도 신규 Scene 생성

**기각 이유**: 기존 `Ingame.unity` 영역 단일 Scene 영역 활용 — Player·UI·Camera·Health·Skill 영역 모두 부착됨. 본 설계 = Ingame.unity 영역 Tilemap·SpawnPoint만 절차 영역 교체.

### 14-6. (기각 6) Boss 방 영역 절차 생성 포함

**기각 이유**: 기획 §3-2 영역 보스 방 = "고정 카메라·후방 차단·전용 지형". 자연 생성 영역 적합 X. 본 설계 = 도입·전개 영역만, 보스 방 = 별도 수동 Scene 또는 본 설계 영역 마지막 chunk 영역 hand-place placeholder.

**보완**: MapGenerationConfig 영역 `bool includeBossRoom` 영역 옵션 + Boss 방 영역 별도 prefab 영역 Instantiate (현 Phase 1 영역 PD 결정 보류·§15-9 영역).

---

## §15. PD 결정 대기 안건 (Phase 2 진입 전 결정 의무)

### 15-1. 기획 SOT 정합 — `프로젝트/EerieVillage/기획/level/01_스테이지_구조.md` §5 OVERRIDE 처리

기획 영역 §5 "전체 수동 배치 + 구간 2 적 배치 소규모 셔플" vs PD 신규 지시 "맵의 길이만 지정하면 랜덤으로 자동 생성" 충돌.

**옵션 A**: 기획 SOT 정정 — level-designer 후속 갱신
**옵션 B**: 두 영역 공존 — 본 설계 절차 생성기 + 기획 수동 배치 영역 두 가지 운용

### 15-2. 사선·이중 점프 도입 여부

현 KinematicObject 영역 2단 점프 X. 본 설계 §6-2 영역 단일 점프 영역만 ValidateJumpReach.

**옵션 A**: 단일 점프만 유지 — 본 설계 그대로
**옵션 B**: 2단 점프 추가 — 본 설계 maxVerticalHeight 약 2배 영역 재산출 의무 (PlayerController.UpdateJumpState 영역 추가 구현)

### 15-3. chunk 영역 옵션 노출 영역

본 설계 §4-4 영역 chunk 영역 platformDensity 가중치 변동 (Intro 0.3·Mid 0.6·BossPre 0.4) 기본값.

**옵션 A**: 기본값 고정 — PD 옵션 노출 X
**옵션 B**: MapGenerationConfig 영역 `ChunkProfile[]` 영역 PD 노출 — 세부 영역 영역

### 15-4. Level/AutoForeground 분리 전략

§7-5 영역 옵션 A (본 설계 영역 직접 분리·권고) vs 옵션 B (GameOptimizer 위임).

### 15-5. 공중 발판 영역 SpawnPoint 배치 여부

§8-3 영역 공중 발판 (폭 ≥3 Tile) 영역 거점 후보 영역.

**옵션 A**: 지면만 거점 — 안전·patrol 거리 보장
**옵션 B**: 공중 발판 영역 거점 — 입체적 전투·플랫포밍 강조 (단·spawn Enemy 떨어짐 위험)

### 15-6. Start Point 좌측 고정 vs random

§8-4 영역 본 설계 영역 항상 좌측 고정. 향후 영역 양 끝 random·역방향 진행 영역.

### 15-7. Enemy 재-spawn (사망 후 시간 경과 재출현)

§9-5 영역 SpawnPoint 1개당 1 Enemy 영역 1회 spawn 영역. 재-spawn 영역 옵션·timer·count 영역.

### 15-8. Enemy spawn 방식 — `Enemy.prefab + MonsterRandomizer` vs `Enemy_M00N` 직접

§12-4 영역 두 영역 영역. 본 PM 권고 = 후자 (catalog 영역 명시적 prefab 영역 가중치).

### 15-9. Boss 방 포함 여부 (현 Phase 1)

§14-6 영역 보스 방 영역 별도 영역 영역 → 본 Phase 1 영역 영역 X. 추후 영역.

### 15-10. 맵 길이 단위 — Tile vs 미터 vs 청크

본 설계 영역 Tile 단위 영역 (`lengthTiles`). 1 Tile = 1 Unity unit 가정.

**옵션 A**: Tile 단위 유지 (개발 영역 정합)
**옵션 B**: 미터·청크 단위 영역 PD 친화 영역 변환 UI 추가

### 15-11. 옵션 노출 영역 — MapGenerationConfig 영역 PD 자유도

본 설계 §4-1 영역 옵션 12종. PD 영역 너무 많은 옵션 영역 = 결정 부담 영역.

**옵션 A**: 12종 모두 노출 (PD 자유도 최대)
**옵션 B**: 5~6종만 노출 (lengthTiles·spawnDensity·platformDensity·stageTheme·includeBossRoom 등) — Advanced 영역 toggle

---

## §16. C50 Phase 2 토큰 옵션 사전 보고

### 16-1. Phase 2 분량 추정

| 단계 | 코드 라인 추정 | 토큰 추정 |
|------|--------------|---------|
| Phase 2-A 데이터 SOT (5종 SO + 인스턴스) | 320 라인 | ~12K |
| Phase 2-B 절차 생성기 (5종 Planner + 테스트) | 530 라인 | ~25K |
| Phase 2-C Editor Window + Applier (3종) | 480 라인 | ~22K |
| Phase 2-D 정합 검증 + 5 stage Config | 100 라인 + 검증 | ~10K |
| **합계** | **1430 라인** | **~69K** |

### 16-2. 옵션 사전 보고 (C50)

**옵션 (a) 단일 Phase 2 일괄 처리**:
- 단일 세션 영역 ~70K 토큰
- 위험: 컨텍스트 영역 한계 영역 일부 정합 영역 영역
- 권고도: 중

**옵션 (b) Phase 2-A/B/C/D 분할 순차 진행** (P32 정합·본 PM 권고):
- 2-A 단독 ~12K → 검증 → 2-B 단독 ~25K → 검증 → 2-C 단독 ~22K → 검증 → 2-D
- 각 단계 영역 클라이언트팀장 Opus 위임·게임플레이 프로그래머 Sonnet 작업·클라이언트팀장 Opus 검증
- 권고도: **상** — Drop-Through 발판 영역 BT22~BT47 28회 시도 영역 교훈

### 16-3. 본 PM 권고

**옵션 (b) Phase 2-A/B/C/D 분할 순차 진행** 영역 PD 승인 의무.

각 Phase 영역 클라이언트팀장 (`client-team-lead`) Opus 영역 위임. 단순 반복 영역 = 게임플레이 프로그래머 Sonnet 영역 직접 위임 가능 (Phase 2-A 데이터 SO 영역 등).

---

## §17. 변경 이력

| 일시 | 변경 | 사유 | 기안 |
|------|------|-----|------|
| 2026-05-18 | v1.0 초안 — Phase 1 설계 완료 | PD 직접 지시 BT13-Dev-Map (2026-05-18) | 개발팀장 (dev-team-lead) |

---

## §18. 종결

본 Phase 1 설계 완료. Phase 2 진입 전 §15 PD 결정 대기 안건 11건 영역 결정 의무. 결정 후 클라이언트팀장 영역 Phase 2-A 위임 진입.

**관련 SOT 영역**:
- 본 문서 = 맵 시스템 설계 SOT (단일)
- 스킬 시스템 영역 영향 X (BT12 정합)
- BT5-Dev 발판 시스템 영역 완전 계승 (GameOptimizer·KinematicObject·EnemyController)
- 기획 §5 자동 생성 vs 수동 배치 영역 PD OVERRIDE 후속 정합 의무 (§15-1)

---

**작성**: 개발팀장 (`dev-team-lead`)
**근거**: PD 직접 지시 BT13-Dev-Map (2026-05-18) + PD 추가 결정 4건 (시각 시뮬레이터·리소스 단위·랜덤성·거점 밀도)
**관련 규칙**: C39-10 사전 실측 의무·C49 표준 프로세스·C50 토큰 사전 승인·P18 설계 문서화·P32 맥락 분할 순차 진행
