# 웹 맵 컴포넌트와 이전 경계

현재 개발·검증 대상은 웹입니다. `godot/`와 기존 ZIP은 이전 시제품을 보관한 것으로, 이번 웹 기능을 추가하거나 다시 내보내지 않았습니다.

## 컴포넌트

| 파일 | 역할 | 엔진 의존 |
| --- | --- | --- |
| `src/domain/hex-map.js` | 육각 좌표·이웃·지역·이동 비용·경로·투영 | 없음 |
| `src/domain/grid-boundaries.js` | 경계 표시 판정·고정 마스크·공유 변 중복 제거·Float32 선 좌표 생성 | 없음 |
| `src/domain/route-network.js` | 경로 종류·근거 필터, 표시 경로의 육로 보호 합집합, 경유 노드 조회 | 없음 |
| `src/domain/water-network.js` | 명시적 수로의 항구 연결·최단 거리·배경/이동 금지 검사 | 없음 |
| `src/domain/structure-placement.js` | 고정·인근 타일 배치, 중복 점유 방지, 수변 회전 | 없음 |
| `src/rendering/structure-models.js` | GLB 노드 변환 반영·크기 정규화·재질 준비 | Three.js |
| `src/rendering/structure-layer.js` | 모델 공유·화면 범위 선택·지형 높이 추종·클릭 | Three.js |
| `src/rendering/harbor-layer.js` | 항구 DOM 표식·이름 충돌 완화·선택 수운 항로 높이 부분 갱신 | Three.js + DOM |
| `src/ui/harbor-controls.js` | 기능·도시 연결 필터·근거·수운 경로 검증 | DOM |
| `src/data/map-repository.js` | 바이너리·메타데이터 읽기, 길이 검사, 경계 자료 지연 로드·캐시 | fetch 사용 |
| `src/rendering/terrain-lod.js` | 고도 메시 재사용·해안 셰이더·상세 구역 로딩·변경 구역 통지·레이캐스트 | Three.js |
| `src/rendering/hex-grid-layer.js` | 경계 정책 캐시, 표시 정책 변경 시 선 재생성·상세 구역 변경 시 높이 부분 갱신 | Three.js |
| `src/rendering/surface-binding.js` | 지형 16×16 구역의 정점 색인·높이 갱신·GPU 부분 업로드 | BufferGeometry 계약 |
| `src/rendering/label-layout.js` | 글자 크기 캐시·수학적 겹침 판정·변경된 스타일만 적용 | DOM |
| `src/debug/performance-panel.js` | 명시적 개발 모드의 CPU·프레임 간격 검사 UI | DOM |
| `src/rendering/place-label-layer.js` | 도시·관문·지역 이름 배치와 겹침 완화 | Three.js + DOM |
| `src/rendering/historical-corridor-layer.js` | 육로·수로·추정·선택 참고선, 경유 거점·이름, 타일별 통로 조회 | Three.js + DOM |
| `src/ui/historical-route-controls.js` | 지역·개별 경로 체크 상태와 DOM 입력 | DOM |
| `app.js` | 컴포넌트 연결, 카메라·입력·편집·화면 상태 | 브라우저 |
| `data/` | 고도·타일·지명·표시 마스크 | 독립 데이터 |
| `tools/` | 오프라인 자료 생성·검증 | Python / Node |

루트 `map-core.js`, `terrain-lod.js`는 이전 import 주소를 유지하는 재수출 파일입니다. 새 코드는 `src/`를 사용합니다. 순수 도메인 코드는 렌더러·DOM·게임 객체를 참조하지 않습니다.

Godot 이전 시 데이터 형식과 도메인 규칙을 그대로 옮기고, 메시·재질·입력·라벨 표시를 해당 엔진의 어댑터로 교체합니다. JavaScript 컴포넌트가 GDScript로 자동 변환되는 것은 아닙니다. 좌표 X 동쪽, Z 남쪽, Y 위쪽, 평면 km·원본 고도 m 계약은 `MAP_RULES.md`를 따릅니다.

## 육각 경계 생략

`tools/build_grid_data.py`는 상세 고도에서 육각 변마다 5점을 조사하고 인접 표본 사이의 최대 `높이차 m / 수평거리 m`를 저장합니다. 절대 고도가 높은 평탄 고원을 숨기지 않습니다. 이 수치는 화면에서 과장된 경계의 기울기 판정용이며 실제 지형의 최대 경사도나 화면 픽셀에서 측정한 변형률은 아닙니다. 카메라 방향에 따른 판정은 하지 않습니다.

```text
표시 기울기 = atan(사전 계산 경계 기울기 × 높이 강조)
표시 기울기 > 선택한 각도 → 해당 타일 경계 숨김
```

먼저 경사 기준으로 후보 마스크를 만듭니다. 일반 후보 타일의 인접 타일 중 숨김이 있으면 그 후보의 경계를 전부 생략합니다. 이 정리는 원래 후보 마스크에 대해 한 번만 적용하여 표시 영역이 반복해서 줄어들지 않게 합니다. 남은 타일의 여섯 변을 모두 생성하고 공유 변은 한 번만 생성합니다. 숨긴 타일끼리의 변과 완전한 육각형에 속하지 않는 선 조각은 생성하지 않습니다. 역사 통로 보호 타일은 예외로 여섯 변 모두 남깁니다. 선택 강조와 게임 이동 가능 여부는 경계 정책과 독립적입니다. [통로 자료·계약](./CORRIDORS.md)에 역사적 근거와 근사 범위를 기록합니다.

- 자동: 높이 강조·허용 각도가 바뀔 때만 가벼운 타일별 비교를 수행합니다. 매 프레임 상세 고도를 다시 조사하지 않습니다.
- 전체: 모든 경계를 생성하여 시각적으로 비교합니다.
- 고정: 저장된 8 KiB 비트 마스크를 읽습니다. 기울기 비교를 하지 않습니다. 저장 시의 높이 강조를 설명에 표시하며 다른 높이 강조에서 자동으로 재판정하지 않습니다.

정책이 같으면 경계 변 목록을 재사용합니다. 카메라 이동만으로 경계 메시를 다시 만들지 않습니다. 상세 고도 구역이 달라지면 변경 구역에 속하는 경계 정점 높이만 갱신합니다. 고정 정책의 높이 강조 변경은 기존 메시의 모든 높이를 갱신하며, 자동 정책의 높이 변경은 표시 변 목록이 바뀔 수 있어 메시를 재생성합니다. 표시한 경로가 바뀌면 채택한 육로 구간의 보호 합집합과 경계 변 목록을 갱신합니다. 경계를 끄면 지연 로딩과 생성 없이 숨깁니다.

## 데이터 계약

| 파일 | 형식 |
| --- | --- |
| `grid-edge-slope-{256,220,128}.u16` | 행 우선 UInt16 LE, 경계 기울기 비율 ×10,000, 올림. 해당 격자 크기²개 |
| `grid-display-profile.json` | version 1, datasetId, surfaceHash, gridSize=256, encoding, visibleBits, parameters, scope |
| `grid-exclusions.json` | 고정 마스크 생성 기준: angleDegrees, exaggeration, tileIds, regions |
| `historical-corridors.json` / `corridor-grid-*.bits` | 역사 통로 참고 좌표·연결된 타일 ID·표시 예외 비트셋. 이동 규칙과 독립 |

`visibleBits`는 LSB-first 비트셋을 Base64로 인코딩합니다. ID `i`는 byte `i >> 3`의 bit `i & 7`이며 1=표시 후보, 0=숨김입니다. 실행 시 통로 보호와 불완전한 후보 정리를 적용하여 최종 표시 마스크를 만듭니다. 후보를 저장하므로 다시 불러와도 반복 정리로 영역이 줄지 않습니다. 65,536타일에 8,192 bytes, JSON에서는 약 11 KiB입니다. `scope=grid-boundaries-only`로 게임 이동·AI·지형 데이터와 구분합니다. datasetId와 상세 고도 SHA-256 surfaceHash가 다르면 불러오기를 거부합니다.

웹에서 ‘표시 마스크 저장’으로 일반 경계의 마스크와 `preserveCorridors` 설정을 저장하고 ‘불러오기’로 재사용합니다. 경로별 표시 상태는 이 파일에 저장하지 않습니다. 실행 시 현재 표시 경로의 육로 보호 합집합을 고정 마스크에도 적용합니다. 전체 경로의 합집합은 별도 비트셋으로도 제공합니다. 이전 파일에 설정이 없으면 보호를 켭니다. 영구 제외할 타일 ID 또는 지역 코드를 `grid-exclusions.json`에 넣은 뒤 다음 명령을 실행하면 기울기 자료를 다시 조사하지 않고 고정 마스크만 갱신합니다. 통로도 제외하려면 보호 옵션을 꺼야 합니다.

```powershell
python tools/build_grid_data.py --freeze-only
```

최종 고도가 달라졌다면 `python tools/build_grid_data.py`로 기울기와 마스크를 함께 다시 생성합니다. 전체 상세 고도 생성 파이프라인도 이 작업과 통로 생성을 마지막에 실행합니다. 현재 영구 제외 목록은 비어 있으며 기본 고정 마스크는 높이 18배·60° 기준의 일반 타일 자동 결과입니다.

## 성능의 범위

투명하게만 그리거나 셰이더에서 픽셀을 버리면 숨겨진 선의 정점 처리가 남을 수 있습니다. 이 구현은 숨길 변을 선 좌표 배열에 넣지 않아 실제 경계 정점·선분·업로드 버퍼를 줄입니다. 일반 경계 1개 메시와 통로 보호 경계 1개 메시를 사용하며, 변을 두 메시 중 하나에만 넣습니다. 통로 보호를 끄면 일반 경계만 그립니다. 두 경계 모두 고도를 따라 배치하고 깊이 검사를 생략하여 허용된 육각형의 변이 지형 뒤에서 다시 끊기지 않게 합니다. 통로 경계는 변당 4선분, 일반 경계는 2선분입니다. 역사 참고선은 육로·수로·추정·선택의 최대 4개 메시로 묶으며 같은 채널의 같은 좌표 구간은 중복 생성하지 않습니다. 경유 성·나루는 DOM 표식이며 3D 건물을 만들지 않습니다.

256 격자·18배·60°·전체 경로 표시·육로 보호에서 전체 404,820선분이 269,062선분으로 줄어듭니다(약 33.5%). 경계 위치 버퍼는 약 9.27 MiB에서 6.16 MiB로 줄어듭니다. 최종 표시 타일은 41,017개이며 불완전한 후보 8,619개는 경계를 전부 생략합니다. 상세 지형 삼각형·고도 텍스처·하천·도시·경로 탐색 작업은 이 마스크로 줄지 않습니다. Node의 1회 CPU 생성 시간은 `exports/grid-validation.json`에 별도 기록하며 GPU·브라우저 FPS 측정으로 해석하지 않습니다.

## 역사 경로의 자료 분리

`historical-corridors.source.json`은 기존 산악 통로, `regional-routes.source.json`은 확장 경로·경유 노드·출처의 편집 원본입니다. `tools/build_corridors.mjs`가 투영 좌표·수륙 구간·연속 타일 체인·육로 보호 목록을 사전 생성합니다. `tools/river-alignment.mjs`는 선택한 이름의 연결된 현대 하천만 수로의 표현에 참고합니다. 다른 지역의 동명 하천이나 연결되지 않은 줄은 연결하지 않습니다. 이 과정은 고대 유로의 고증이나 이동 허용 판정과 별도입니다.

렌더러는 선택 경로 ID·표시 목록·지형 구역 상태가 달라질 때만 참고선 좌표를 갱신합니다. 카메라 이동에는 좌표 재투영과 transform 갱신을 수행하고, 이름 겹침은 최대 약 80ms 간격 및 이동 종료 시 판정합니다. 글자 크기는 캐시하며 도시 이름의 충돌 사각형을 경유지 레이어에 전달합니다. 근거가 없는 연결 추정선·수로는 육로 보호 목록에서 제외합니다. [전체 계약과 근거](./CORRIDORS.md), [경로별 목록](./ROUTE_CATALOG.md)을 확인하세요.

수륙이 번갈아 이어지는 경로는 `legs`로 표현하고, 동시에 다른 노선을 이용하는 병행 경로는 별도 `branches[]`로 표현합니다. 편집 원본의 `parallelPaths[]`를 빌드하면 각 branch에 독립된 points·legs·tiles·gridTiles가 생성됩니다. `referencePaths()`는 주 경로와 병행 경로를 반환합니다. 서로 다른 좌표열을 하나로 이어 가상의 연결 선분을 생성하지 않습니다. 부모 경로의 gridTiles는 병행 육로까지 합친 보호 합집합이며, 이름 배치·지역 확대·타일별 조회도 병행 경로를 포함합니다. 이릉의 수로와 자귀–이도·효정 육로를 이 구조로 표시합니다.

최종 고정 마스크는 실행 중 기울기 판정을 없앱니다. 경계 좌표까지 구역별로 미리 만들어 저장하면 로딩 때의 좌표 생성도 없앨 수 있습니다. 경계 생성 이후의 변 목록 순회·버퍼 업로드·그리기는 여전히 필요합니다.

게임 연산까지 줄이려면 별도 데이터로 다음 범위를 정의해야 합니다.

| 데이터 | 생략할 작업 |
| --- | --- |
| 경계 표시 마스크 | 경계 선 좌표·정점·선분 |
| 이동 가능 그래프 | 배경 지역의 경로 확장·방향별 이동 후보 |
| 게임 객체 활성 구역 | AI·생산·보급·전투·이벤트 업데이트 |
| 표시 구역·LOD | 필요 없는 상세 메시·자료 로딩, 배경의 저해상도 표시 |

게임 이동을 금지하는 플래그를 추가해도 계속 모든 객체를 업데이트하거나 지형을 그리면 그 작업은 줄지 않습니다. 현재의 표시 마스크를 게임 논리 제외 마스크로 사용하지 않습니다.

## 검증

`node tools/verify-grid.mjs`는 모든 표시 타일의 여섯 변 완성, 완전한 타일에 속하지 않는 선 조각 생략, 공유 변 중복, 표시 기울기·높이 강조 관계, 비트셋 왕복, 실제 데이터의 자동/고정 결과 일치, 데이터 버전 오류를 검사합니다. 웹에서는 전체·자동·고정 전환과 상세 지역을 비교합니다.

## 2026-10-04 최적화 시도

`OPTIMIZATION.md`에 백업, 측정 조건, 적용 범위와 남은 비용을 기록합니다. 게임 규칙과 지형 정밀도, 상세 구역 선택 기준, 삼각형 수는 유지합니다. 지형은 원본 고도·전체 인덱스를 보관하여 높이·색·활성 영역 중 바뀐 속성만 갱신합니다. 높이 변경 때 법선은 전체 면 기준으로 계산해 상세 구역의 경계 음영이 이전 버전과 같게 유지됩니다. 지형의 새 scope 판정은 scopeKey가 변경됐을 때만 수행합니다.

`SurfaceBinding`은 지형과 같은 16×16 구역의 표본 색인을 저장합니다. 격자·하천·역사 참고선의 수평 좌표와 연결을 재사용하고 LOD가 바뀐 구역의 고도만 갱신합니다. 하천 리본은 폭 방향 모서리 대신 기존 중심선에서 높이를 표본합니다. 전체 GPU 업로드가 아직 대기 중이면 후속 부분 갱신으로 업로드 범위를 축소하지 않습니다. 구역별 객체로 쪼개 화면 밖 선을 그리지 않는 기능은 이번 시도에 포함하지 않았습니다.

높이 입력은 100ms 동안 멈추거나 입력을 확정할 때 적용합니다. 정상 화면에서는 성능 검사 UI와 측정 루프가 활성화되지 않습니다.

## 항구와 전략 수운

[PORTS.md](./PORTS.md)에 43개 거점의 권역 좌표, 도시 연결, 기능 등급, 원문 근거를 기록합니다. `historical-harbors.source.json`은 편집 원본, `historical-harbors.json`은 투영·타일·지역을 포함한 실행 데이터입니다. 추가 페이로드는 약 27 KiB이며 DEM·격자 밀도를 바꾸지 않습니다. 항구 표식은 DOM으로 추가 draw call이 없고, 선택 수운선만 배치한 LineSegments 메시 1개를 사용합니다. 이름 배치는 기존 크기 캐시와 충돌 판정을 공유합니다.

수로 그래프는 `historical-corridors.json`의 연속된 water 구간과 명시적 `routeNodeId` 연결에서만 만들어집니다. 가까운 항구·다른 강을 자동으로 잇거나 육상 구간을 수로로 바꾸지 않습니다. 지도상의 on/off는 표시 필터이며 경로 검증의 통행권과 독립적입니다. 강하의 게임 도시 ID와 기존 하구의 역사 경유 ID는 `placeId`로 연결합니다. 수군 수송 정원·정치 소유권은 기능 등급에서 추론하지 않습니다.

## 사용자 제공 시설 모델

모델 원본은 `assets/models/structures/`, 편집 규칙은 `data/structure-models.source.json`, 생성된 배치는 `data/structure-placements.json`에 저장합니다. 원래 역사 좌표와 실제 게임 표시 좌표·타일 점유를 분리합니다. 육상 73개 타일은 중복되지 않고 수변 43곳은 원래 좌표를 유지합니다. 원본 노드 변환을 반영한 지오메트리는 종류별로 공유하고 화면 안의 인스턴스만 그립니다. 상세 지형 구역이나 높이 강조가 바뀔 때 해당 시설의 높이를 다시 표본합니다. 카메라 이동은 표시 인스턴스 선택이며 모델 지오메트리를 다시 생성하지 않습니다. 배치 계약·검증·백업은 [STRUCTURES.md](./STRUCTURES.md)에 기록합니다. 기존 Godot 예제에는 적용하지 않았습니다.
