# ADR-0008 — 60fps 성능 예산 (Phase 3)

상태: **Accepted (2026-05-08, 야간 자율)**

---

## 결정

8763 부품 × 16.67ms (60fps) 환경에서 SimulationLoop + EventBus + Transform 동기화의 자율 추정 부하를 정리.
실제 측정은 사용자 PC GUI 필요 — Phase 3 자율 진행 영역 외.
정량 추정만 자율 작성, DOTS/ECS 도입 임계점 결정.

## 1프레임 16.67ms 예산 분배 (목표)

| 영역 | 예산 | 비고 |
|---|---|---|
| Unity Editor/Player overhead | 3 ms | 메인 루프, GC, 그래픽스 명령 |
| Render (URP, 8763 mesh) | 6 ms | DrawCallBatching + GPU instancing 의존 |
| **factory00 Update (시뮬)** | **5 ms** | 본 ADR 대상 |
| 여유 | 2.67 ms | 입력·UI Toolkit reflow |

본 ADR은 5ms 안에 시뮬 + EventBus + Transform sync 끝내야 한다는 가정.

## SimulationLoop 부하 추정 (Phase 1+2 코드 기준)

`SimulationLoop.Update()` 흐름:

1. **movement 부품 순회** — `for p in Parts` (8763 회)
   - `if (!p.movable) continue` 빠르게 skip
   - 활성 motor 부품: 가정 100개
   - delta 계산 + Part.position/rotation 갱신
   - `Bus.Publish(MotionEvent)` → ConstraintGraph.OnMotion → 자식 8762개 중 평균 depth=4 자식 traversal
2. **Transform 동기화 순회** — `for p in Parts` (8763 회)
   - `_partTransforms.TryGetValue(p.id, out t)` — Dictionary lookup
   - `t.localPosition = p.position` + `t.localRotation = Quaternion.Euler(p.rotation)`

### 추정 cost (사용자 PC i5/i7 기준 가정)

| 항목 | 시간/iter | 8763 iter | 메모 |
|---|---|---|---|
| `for` skip (movable=false 8663개) | 5 ns | 0.04 ms | 분기 예측 hit |
| motor delta 계산 (100개) | 200 ns | 0.02 ms | Vector3 산수 |
| `EventBus.Publish` (motor → 평균 5 children 전파) | 1 us | 0.5 ms | delegate Invoke + Dictionary lookup |
| `ConstraintGraph.OnMotion ApplyConstraint` (5 children × 100 motor = 500회) | 200 ns | 0.10 ms | 단순 vec add |
| Transform sync 순회 (8763 iter) | 800 ns | **7.0 ms** ⚠ | Dictionary + Quaternion.Euler |

**예측 결론**: Transform 동기화 순회가 **5ms 예산 초과** 가능. motor 100개 가정 시 시뮬 본 로직은 0.7ms로 충분하지만 Transform sync가 병목.

## 최적화 후보 (Phase 3 자율 코드 가능)

### 옵션 A. Dirty flag — 갱신된 부품만 sync

`Part.dirty` 플래그 추가 → SimulationLoop가 모션 적용 시 dirty=true → sync 순회는 dirty만 처리.
8763 → 평균 100~500 부품으로 축소 → Transform sync 0.5ms 미만 가능.

### 옵션 B. Transform 직접 참조

`Part`에 `Transform cachedTransform` 추가 → Dictionary lookup 제거. 직렬화 시 무시 (`[NonSerialized]`).

### 옵션 C. DOTS/ECS 마이그레이션 (Phase 4 이상)

Job + Burst → 8763 entity transform sync 0.1ms 가능. 하지만 mass refactor 비용 큼. 60fps 미달 측정 후 결정.

### 옵션 D. Quaternion 캐싱

`Quaternion.Euler(p.rotation)` 매 프레임 호출 → 회전 변화 없으면 캐시. 옵션 A와 함께 적용.

## 결정 — 자율 적용

**옵션 A + B + D 적용** (Phase 3 자율 진행 가능).
Part.dirty 플래그 + Transform 캐시 + Quaternion 캐시 = **추정 sync 0.3ms 미만**.

DOTS/ECS는 사용자 측정 후 Phase 4 결정.

## 측정 가이드 (사용자 PC)

```
1. Unity Editor → Window → Analysis → Profiler
2. Play 모드에서 8763 부품 어셈블리 임포트 후 motor 100개 활성
3. CPU Usage / Hierarchy / SimulationLoop.Update 검색
4. 평균 ms 측정. 5ms 미만이면 통과.
```

목표:
- **5ms 미만**: 정상
- 5~10ms: 옵션 A 적용 (Phase 3 자율 가능)
- 10~16ms: 옵션 B + D 추가 (Phase 3 자율 가능)
- 16ms 초과: 옵션 C DOTS 마이그레이션 (Phase 4)

## Render 비용

URP DrawCall 8763 mesh 직접 그리면 GPU bound. GPU instancing이 필수 — glTF 임포트 시 같은 sharedMesh를 가진 인스턴스가 평균 3.7배 → 실 DrawCall 약 945개 + materials 75 = batch 가능.

## 참고

- ADR-0006 — Phase 3 범위
- `Assets/Scripts/Game/SimulationLoop.cs`
- `Assets/Scripts/Importers/GltfPartImporter.cs` (sharedMeshId 인스턴싱)
