# Half‑Life 2의 문이 만든 충격적인 버그, 부동소수점(FP) 정밀도 문제를 파헤치다

*SEO 키워드: **Half‑Life 2 버그**, **부동소수점 오류**, **게임 개발**

---

# # 서론 – “왜 이렇게 작은 문이 큰 문제를 일으켰을까?”

1998년 발매된 **Half‑Life 2**는 물리 엔진과 스토리텔링으로 게임 역사를 바꾼 작품으로 유명합니다. 그런데 이 명작에도 예상치 못한 **버그**가 존재한다는 사실, 들어보셨나요?

특정 맵에 배치된 한 개의 문이 플레이어가 지나가면 게임이 멈추거나 캐릭터가 벽을 통과하는 현상이 발생합니다. 원인은 바로 **부동소수점(Floating‑Point) 정밀도 오류**였습니다. 이 글에서는 그 원리를 초보자도 이해하기 쉽게 설명하고, 같은 실수를 피하기 위한 개발 팁을 공유합니다.

---

# # 본론

## # 1️⃣ 부동소수점이란? – 컴퓨터가 실수를 저장하는 방식

- **정의** : 실수(소수점 이하를 포함한 숫자)를 근사값으로 저장하는 32비트(단정도) 혹은 64비트(배정도) 형식

- **특징** : 무한히 정확한 실수를 표현할 수 없으며, 작은 오차가 누적될 수 있다.

- **예시** : 0.1을 2진수로 표현하면 무한히 반복되므로, 실제 메모리에는 0.10000000149011612와 같은 근사값이 저장된다.

이러한 오차는 눈에 보이지 않을 정도로 작지만, **거리 연산**이나 **충돌 판정**처럼 수많은 연산이 반복될 때 눈에 띄는 오류로 나타날 수 있습니다.

## # 2️⃣ Half‑Life 2에서 문이 만든 오류의 구체적 메커니즘

1. **문 위치와 회전값 저장**

   - 문은 월드 좌표계에 `Vector( x, y, z )`와 `Angle( pitch, yaw, roll )` 로 저장됩니다.

   - 엔진은 이 값을 부동소수점으로 관리합니다.

2. **플레이어와 문 사이의 거리 계산**

   - 플레이어가 문에 접근하면 `distance = sqrt( (dx)^2 + (dy)^2 + (dz)^2 )` 로 충돌 여부를 판단합니다.

   - 여기서 `dx, dy, dz`는 두 좌표의 차이이며, 차이가 아주 작을 경우 부동소수점 오차가 크게 작용합니다.

3. **오차 누적 → 충돌 판정 실패**

   - 문이 아주 얇고, 플레이어가 아주 천천히 이동하면 `dx, dy, dz` 값이 0에 근접합니다.

   - 이때 부동소수점 오차가 `distance` 를 실제보다 작게 만들거나, 반대로 크게 만들어 **충돌 판정이 틀려** 문을 통과하거나 게임이 멈추는 현상이 발생합니다.

> **핵심 포인트** : “작은 오차 = 큰 문제”는 물리 기반 엔진에서 흔히 마주하는 함정이며, Half‑Life 2의 이 버그는 그 대표적인 사례입니다.

## # 3️⃣ 실제 코드 예시와 문제 재현 방법

```
cpp

// 문과 플레이어의 충돌 체크 (단순화된 예시)

Vector doorPos = GetDoorPosition();      // 부동소수점 Vector

Vector playerPos = GetPlayerPosition();  // 부동소수점 Vector

Vector diff = doorPos - playerPos;

float dist = diff.Length();              // sqrt(dx*dx + dy*dy + dz*dz)

// 충돌 임계값

const float COLLISION_EPS = 0.01f;        // 1cm

if (dist < COLLISION_EPS) {

    // 정상적인 충돌 처리

    ResolveCollision();

}

```

- `COLLISION_EPS` 값을 **너무 작게** 잡으면 `dist` 가 `0.0099999` 와 같은 부동소수점 근사값이 되어 판정이 실패한다.

- 해결책은 **EPS 값을 충분히 크게** 잡고, 필요 시 `double`(배정도) 로 연산하거나 **정수 좌표**(fixed‑point) 로 변환하는 것이다.

## # 4️⃣ 게임 개발자가 배워야 할 교훈

| 교훈 | 적용 팁 |

|------|----------|

| **오차를 무시하지 말라** | 물리 연산에 사용되는 값은 최소 1e‑4 정도의 여유를 두자. |

| **정밀도 선택을 신중히** | 거리 계산이 중요한 경우 `float` → `double` 로 전환하거나 고정소수점 방식을 고려한다. |

| **테스트 시 다양한 속도·각도** | 빠른 이동·느린 이동·극단적인 회전 등 모든 경우를 시뮬레이션해 버그를 조기에 발견한다. |

| **디버그 로그 활용** | `printf` 나 엔진 로그에 `dist` 값을 출력해 오차가 어느 정도인지 시각화한다. |

## # 5️⃣ 비슷한 사례와 최신 엔진의 대응

- **Unreal Engine** : `FMath::IsNearlyZero` 함수를 제공해 부동소수점 근사값을 안전하게 비교한다.

- **Unity** : `Mathf.Approximately` 로 부동소수점 비교를 권장한다.

- **Source Engine** (Half‑Life 2 기반) : 이후 업데이트에서 `float` 대신 `double` 로 변환한 물리 서브시스템을 도입했지만, 레거시 맵에서는 여전히 문제가 남아 있다.

---

# # 결론 – 버그를 교훈으로, 더 견고한 게임을 만들자

Half‑Life 2의 문 버그는 **“작은 오차가 큰 파멸을 부른다”**는 사실을 명확히 보여줍니다. 부동소수점 정밀도 문제는 어느 엔진, 어느 프로젝트에서도 발생할 수 있는 위험 요소이니, **초기 설계 단계부터 정밀도와 충돌 판정 로직을 꼼꼼히 검증**하는 것이 중요합니다.

> **행동 유도** : 여러분이 현재 진행 중인 게임 프로젝트에 부동소수점 오류가 의심된다면, 오늘 당장 `EPS` 값을 재조정하고 `double` 로 전환해 보세요. 그리고 이 글을 공유해 다른 개발자와 함께 **안정적인 게임 개발 문화를** 만들어갑시다!
