# 보행 메시 검수와 콘텐츠 작성 원본

2026-10-03. 계단·벤치의 기존 문제 좌표를 실제 앱에서 재현하고, Sunnyvale 가상 시대 시험의 작성 원본을 생성 결과에서 분리했다. 보행 메시를 수정하거나 실제 역사 자료를 제작한 결과는 아니다.

**후속 수정:** 아래는 보정 전 조사 기록이다. 이후 촬영된 현관 마루와 충돌면을 대조해 국소 바닥을 보정했고, 세 제어기에서 벤치 왕복과 끝 난간 충돌을 확인했다. 최신 결과는 [현관 보행 바닥 보정](PORCH-WALKMESH-REPAIR.md)을 참고한다.

## 1. 보행 문제 확인

다운로드 폴더의 예전 위치 JSON은 현재 없어서 `sunnyvale.test.ts`에 보존된 사용자 좌표를 사용했다. 시작 위치만 시험 좌표로 바꾸고 원본 SOG·충돌 GLB와 앱 제어기는 그대로 사용했다. 마우스 회전 없이 같은 방향 키를 입력했다.

| 시험 | Three.js BVH | Three.js Rapier | PlayCanvas Ammo |
|---|---|---|---|
| 계단에서 왼쪽 직진 | 1초에 상부 구간 도달 | 1초에 상부 구간 도달 | 1초에 높이는 올라갔지만 이동 거리가 짧음. 추가 2초 직진 시험에서 상부 구간 도달 |
| 벤치 부근에서 전진 1초 → 후진 1초 | 낮은 위치에서 복귀 막힘 | 낮은 위치에서 복귀 막힘 | 낮은 위치에서 복귀 막힘 |
| 복귀 후 키를 놓고 2초 대기 | 위치 변화 0 | 위치 변화 0 | 약 0.14mm 변화 |

벡터 방향은 계단 A/D, 벤치 W/S이며 시선 회전은 `[0,0,0]`이다. BVH·Rapier의 같은 좌표에 대한 60/20 FPS 오프라인 시험도 계단 상승과 벤치 복귀 실패를 재현했다. 기존 40초 정지 회귀 테스트도 유지했다. 이번 결과로 모든 계단이나 경로가 안전하다고 판정하지 않는다.

### 벤치 위치의 충돌면

벤치 시험 시작 발 위치는 `[-13.66356, 0.53731, -21.88231]`m다. X를 고정하고 Z를 5cm씩 줄이며, 처마가 아닌 발 위 15cm에서 아래로 충돌면을 탐색했다.

| X | Z | 충돌면 Y | 면 기울기 |
|---:|---:|---:|---:|
| -13.66356 | -21.98231 | 0.46143 | 29.84° |
| -13.66356 | -22.03231 | -0.22668 | 2.81° |

인접 표본 두 개의 높이가 **약 68.8cm** 다르다. 현재 턱 오르기 한도는 20cm다. 내려간 뒤 돌아오려는 곳에서 벽처럼 막히는 결과가 세 제어기에서 나왔다. 복귀 후 발 높이는 BVH 약 -0.216m, Rapier 약 -0.206m, Ammo 약 -0.216m이며 시작 높이는 약 0.537m다. 복귀 거리는 모두 시작점에서 약 0.84m 떨어져 있다.

이는 공통 충돌 자산의 검수 지점이다. 표본은 가장 먼저 만난 삼각형의 높이이며, 그 면이 의도한 보행 바닥이라는 뜻은 아니다. 촬영된 마루·벤치·난간과 충돌면을 겹쳐 보고, 실제 마루의 누락인지 이동해서는 안 되는 구간인지 판단해야 한다. 세 엔진에서 같은 증상이므로 엔진을 바꾸는 것만으로 해결될 근거가 없다.

### 수정 기준

1. 실제 바닥을 확인해 보행용 면을 별도로 만든다. 가구·난간·벽은 보행 바닥과 구분해서 검수한다. 원본 충돌 GLB의 세밀한 형상이 곧 좋은 보행 면이라는 전제를 두지 않는다.
2. 바닥 누락이면 실제 높이에 맞춰 면을 보정한다. 난간 너머 등 접근 금지 구간이면 그 경계를 막는다. 사진 확인 없이 수평 바닥을 덧대지 않는다.
3. 수정한 충돌을 기존 시각 자산과 같은 월드 좌표에 놓고 계단 직진·벤치 왕복·40초 정지를 확인한다. 현재 20cm 한도를 전체 장소에서 높이는 것은 이 단차를 가리는 수정이므로 하지 않는다.
4. 분할 장면도 사용한다면 보정한 원본 충돌을 다시 분할하고 자산 매니페스트·해시를 갱신한다. 기존 분할 검사는 원본 면 보존을 검사하므로 잘못된 원본 면까지 그대로 보존한다.

**보정 전 벤치 왕복은 실패 상태였다.** 높이가 바뀌어도 평지에 서 있으므로 추락 복귀 장치가 작동하지 않은 것이며, 자동 복귀 기능이 이 문제를 해결했다고 보지 않는다. 후속 수정에서는 현관 안쪽의 누락된 보행 높이를 별도 충돌 바닥으로 보정했다.

### 벽 비침과 구분

사용자가 신고한 창문 아래 벽의 비침은 이번 보행 검수로 해결되지 않았다. 같은 위치에서 양쪽 렌더러에 보인다는 관찰은 공통 Splat 자산의 표현·불투명도를 조사할 이유지만, 실내 가구가 실제로 비친다는 증거는 아니다. 충돌 GLB는 이동·핫스팟 가림에 사용하며 Splat 렌더링을 불투명한 벽으로 바꾸지 않는다. 해당 시점의 원본 Splat과 불투명 표면의 필요성은 별도 시각 검수 항목이다.

### 증거와 재현

결과는 [측정 폴더](measurements/2026-10-03/walkmesh-authoring/)에 저장했다.

- `walkmesh.json`: 실제 충돌 해시, 높이 표본, 60/20 FPS의 BVH·Rapier 경로 12개. 렌더링·Ammo·성능 측정은 포함하지 않는다.
- `bvh/rapier/ammo-browser.json`: 실제 앱의 시작·방향 전환·복귀·대기 상태와 환경. Chrome 154, headed Chromium, ANGLE Metal Apple M4 Pro, 전면·가시 상태였다.
- `ammo-stairs-extended.json`: Ammo의 2초 직진 후 상부 도달 확인.
- 각 구성의 계단·벤치 PNG와 `walkmesh-driver.js`, 추가 계단 확인용 `ammo-stairs-driver.js`.

브라우저는 승인된 Playwright MCP로 앱 하나씩 열고 종료했다. 브라우저 결과의 릴리스는 `local-4d5905679d2e-89a5df9eff9e`이며 이번 작성 도구 변경 전 앱이다. 동기 물리 처리 시간이나 프레임 p95를 비교한 시험이 아니다. `climbedStairs`는 1초 후 X·Y 임계값으로 판정한 진단 값이어서 Ammo의 첫 `false`를 계단 통과 불가로 해석하면 안 된다.

오프라인 재현:

```sh
pnpm audit:walkmesh --output /tmp/atlas-walkmesh.json
```

## 2. 작성 원본과 생성 결과

### 구현한 경로

| 역할 | 파일 |
|---|---|
| 현재 촬영 배경·분할 설정 | `shared/public/world/sunnyvale-stream.json` — 기존 분할 생성기의 결과 |
| 가상 시대 벽·충돌·핫스팟·설명 작성 원본 | `shared/source/sunnyvale-era.author.json` |
| 검사·결합 생성기 | `scripts/compile-sunnyvale-era.mjs` |
| 생성된 월드 | `shared/public/world/sunnyvale-era.json` |
| 생성된 설명 | `shared/public/world/content.json` 중 `sunny-era-` 항목 |

원본에는 `objects`, `colliders`, `hotspots`, `content`, `review`를 둔다. 생성 결과를 직접 수정하지 않는다. `sunny-era-`는 이 생성기의 예약 콘텐츠 이름공간이다. 재생성은 다른 콘텐츠를 보존하고, 원본에서 삭제한 예약 항목은 출력에서도 제거한다. 다른 장면에서 이 이름공간의 항목을 소유하지 않는다.

현재 촬영 분할을 과거에도 재사용하므로 이 원본은 `purpose: illustrative-fixture`다. 가상 과거를 역사 복원으로 표시하거나 다른 콘텐츠 이름공간을 덮어쓰는 입력을 거부한다. 핫스팟 설명 참조·구역·자산·좌표·ID·예산을 기존 공통 검사기로 검증하고, 추가한 충돌에는 `transitionBounds`를 요구한다. 경계가 실제 형상을 감싸는지와 시각·충돌 정합은 육안 검수가 필요하다.

### 수정·생성·검수 순서

```sh
# 1. shared/source/sunnyvale-era.author.json을 편집
pnpm authoring:compile

# 2. 데이터·테스트·타입·양쪽 빌드·릴리스 검사
pnpm check

# 3. 생성된 프로덕션 산출물을 실행
./dev.sh --preview both
```

`node scripts/compile-sunnyvale-era.mjs --check`는 원본과 출력이 일치하는지만 확인하고 파일을 쓰지 않는다. `pnpm check`와 `pnpm build:spikes`에 이 검사를 넣었다. 릴리스 소스 해시에 `shared/source/`도 포함한다. 원본을 변경했는데 생성을 빠뜨리면 빌드 전에 실패한다. 예전 `make-sunnyvale-era.py`는 같은 생성기를 호출하는 호환 진입점이다.

실제 입력 검사가 실패하면 월드·설명 출력은 모두 보존된다. 이 도구는 빌드 전 오프라인 생성기다. 두 파일의 쓰기는 파일 시스템 전체를 하나의 트랜잭션으로 묶지 않았으므로 쓰기 도중 I/O 실패나 개발 서버에서 읽는 경우까지 원자성을 보장하지 않는다. 생성 성공 후 검사·빌드를 완료한 산출물을 미리보기한다.

### 검수 기록

현재 원본의 검수 상태는 `draft`다. 시험이 통과했다고 자료 검수 완료로 바꾸지 않았다. 검수자가 확인한 뒤 `review.status`를 `reviewed`, `review.reviewer`를 이름, `review.date`를 실제 `YYYY-MM-DD` 날짜로 기록한다.

```sh
node scripts/compile-sunnyvale-era.mjs --check --require-review
```

이 선택 검사는 미검수 원본을 거부한다. 일반 미리보기 빌드는 초안을 허용한다. 검수 상태는 작성 기록이며 인증·권한 통제나 역사적 정확성의 자동 판정이 아니다. 실제 배포에 검수 완료를 필수로 하려면 그 배포 명령에 이 조건을 적용해야 한다. 지금 공개 배포는 하지 않는다.

## 3. 실제 역사 콘텐츠 한 구역의 제작 절차

현재 구현은 가상 변경 입력을 보존하는 단계다. 실제 과거 건물·바닥이나 역사 출처를 만들어 넣지는 않았다. 한 구역을 제작할 때 다음 자료와 검수가 필요하다.

| 단계 | 입력·산출물 | 완료 판단 |
|---|---|---|
| 기준 공간 선택 | 현재 촬영 자료, 한 구역의 경계, 안전 시작점, 미터·Y 위·-Z 전방 기준 변환 | 두 앱에서 같은 위치와 크기로 표시 |
| 시대·근거 선정 | 시대 범위, 건물·바닥별 사진·도면·문헌, 출처 ID·제목·연결, 확인 사실과 추정 구분 | 어떤 형상·설명이 어떤 근거에 의존하는지 검수자가 확인 |
| 시대별 시각 제작 | 현재 촬영 SOG, 과거 GLB/SOG, 저해상도 표현, 동일 좌표의 변환 | 실제 과거 자산을 사용한 것과 촬영 배경을 재사용한 것을 구분 |
| 보행·충돌 제작 | 시대별 바닥·계단·벽·가구 충돌, 전환 겹침 범위·안전 위치 | 계단 직진, 왕복, 정지, 좁은 통로, 시대 전환 후 겹침·낙하 확인 |
| 구역·로딩 정의 | 현재/과거 상세·원거리·충돌의 대응, 바운드·점 수·파일 크기·합산 예산 | 경계 이동·해제·실패 복구 시 바닥과 설명이 끊기지 않음 |
| 설명 배치 | 핫스팟 위치·시대·소유 구역·거리, 설명·표현 종류·확신 수준·출처 | 가림·거리·시대에 맞게 열리고, 교체·해제 뒤 이전 패널 정리 |
| 수정 시험 | 설명 하나 변경, 벽 위치 변경과 충돌·겹침 범위 동반 변경, 재생성·검사 | 변경 보존, 다른 콘텐츠 보존, 오류 입력의 출력 보존 |
| 검수 완료 | 자료 담당·공간 담당 확인, 날짜·남은 문제·사용 자산 해시 기록 | 준비한 범위에서 두 엔진과 대상 실기기를 확인 |

시대 범위와 형상별 근거 연결은 현재 작성 원본에 구조화된 필드로 구현하지 않았다. 실제 자료를 넣는 단계에서 그 계약을 추가하고, 과거 자산을 촬영 배경과 분리해야 한다. 현재 가상 시험의 제한을 해제하는 것만으로 역사 콘텐츠가 완성되지 않는다.

### 사람의 작업량 기록

한 구역의 최초 제작과 후속 변경을 별도로 기록한다. 아래 표는 견적이 아닌 기록 양식이다.

| 작업 | 담당 | 실제 작업 분 | 자동 변환 대기 초 | 수정 반복 수 | 남은 문제 |
|---|---|---:|---:|---:|---|
| 자료 정리·근거 검수 | 미기록 | 미측정 | 미측정 | 미측정 | 실제 자료 필요 |
| 시각 자산 정합·LOD 검수 | 미기록 | 미측정 | 미측정 | 미측정 | 실제 과거 자산 필요 |
| 보행 메시 제작·왕복 검수 | 미기록 | 미측정 | 미측정 | 미측정 | 현관 국소 보정 완료·전체 경로 검수 남음 |
| 설명·핫스팟 수정·생성 | 미기록 | 미측정 | 미측정 | 미측정 | 작성 원본 경로 구현 |
| 양쪽 앱·실기기 확인 | 미기록 | 미측정 | 미측정 | 미측정 | 이번 변경의 iPhone 검증 없음 |

기록에는 사용한 원본·자산·릴리스 해시와 검수 경로를 함께 남긴다. 자동 생성 시간과 사람의 제작 시간을 합쳐 인건비로 환산하지 않는다. 같은 공통 데이터를 사용할 수 있는 범위와 엔진별 추가 수정 작업을 나눠 기록해야 실제 개발 비용 비교에 사용할 수 있다.

## 다음 적용

이번 확인으로 보행 문제를 엔진 선택만으로 해결하기 어렵다는 근거와, 가상 시대 데이터를 수정·재생성하는 경로를 확보했다. 벤치 부근은 후속 보정과 왕복 검증을 완료했다. 다음은 신고된 벽 비침의 시각 검수와 전체 보행 경로 검수다. 실제 역사 자료 한 구역을 확보하면 위 제작·수정 순서를 적용해 사람의 시간을 기록한다. 새 결합 장면의 iPhone 재검증, 실제 GPU 메모리와 원본 SH3 모바일 해독 한계는 별도 미해결 항목으로 유지한다.

## 이번 변경의 검증

`pnpm check`에서 74개 테스트(도구 17, 공통 31, Three.js 26), 공통·양쪽 앱 타입 검사, 자산 해시 검사와 양쪽 릴리스 빌드·산출물 검사를 통과했다. Python 호환 실행기의 `--check`도 통과했다. 새 릴리스는 `local-4d5905679d2e-ef10bbe455c0`이며 배포하지 않았다.

새 빌드를 headed Playwright에서 BVH·Ammo로 하나씩 열어 현재 설명 → 과거 전환 → 과거 설명 → 현재 복귀를 확인했다. 양쪽 모두 설명이 열리고 전환 때 이전 패널이 정리됐다. `bvh-authoring-smoke.json`, `ammo-authoring-smoke.json`, `authoring-smoke-driver.js`와 `verification.json`에 기록했다. Rapier는 앞선 보행 조사에서 확인했고 이 후속 설명 시험을 반복하지 않았다.

콘솔에는 양쪽의 favicon 404와 PlayCanvas의 기존 `sortKey overflow` 경고 3회(각 1 splat)가 남았다. 이번 변경으로 해결됐다고 보지 않는다. 테스트 브라우저는 종료했고 로컬 서버는 유지했다.

후속 바닥 보정에서는 `pnpm check`의 77개 테스트와 양쪽 빌드가 통과했다. 새 미리보기 릴리스는 `local-4d5905679d2e-8887379fa947`이다. 보정 전 74개 테스트와 위 경고 기록은 당시 결과로 보존한다.
