← Blog디버깅 전쟁 일지· 1/7

함정 7척을 마리아나 해구로 가라앉힌 한 줄 — substring 매칭의 함정

수면 부력 셋업이 식별자에 'UUV'가 들어있는지 하나로 판정했고, 잠수함 식별자엔 'UUV'가 없다는 이유로 함대 전체의 부력이 통째로 꺼져 7척이 -2989m로 침몰한 사건.

약 2분
debuggingbuoyancymaritimewar-storyroot-cause

함정 7척을 마리아나 해구로 가라앉힌 한 줄

어느 날 해상 시뮬레이션을 돌렸더니, 함대 7척이 전부 바다 밑 약 2,989m로 가라앉았습니다. 마리아나 해구 깊이입니다. 분명 어제까지 잘 떠 있던 배들이었습니다.

부력 static-fallback 버그 — 함정 고도 z의 before/after. 부력이 꺼진 채(빨강)는 2초 자유낙하 후 -2989m로 침하하고, 부력 적용 후(시안)는 +0.9m 수면에 정착한다.부력 static-fallback 버그 — 함정 고도 z의 before/after. 부력이 꺼진 채(빨강)는 2초 자유낙하 후 -2989m로 침하하고, 부력 적용 후(시안)는 +0.9m 수면에 정착한다.

첫 단서 — "전부, 똑같이"

물리 버그를 만나면 가장 먼저 보는 건 재현성입니다. 7척이 제각각 다른 깊이로 가라앉았다면 물리 파라미터(질량·관성)를 의심했을 겁니다. 그런데 7척이 전부, 거의 똑같은 패턴으로 침하했습니다.

💡 "deterministic = 파라미터가 아니다" — 모든 개체가 동일하게 실패하면 그건 개별 물리값 문제가 아니라, 그들을 한꺼번에 처리하는 공통 분기 문제입니다. 침하 궤적이 7척 모두 판박이라는 것 자체가 "부력이 통째로 안 켜졌다"는 신호였습니다.

z 궤적을 보면 명확합니다 — 스폰 직후 약 2초간 중력에 자유낙하(z≈-19m)한 뒤, 상승력이 0이라 끝없이 가라앉습니다. 부력이 애초에 적용되지 않은 것입니다.

진짜 원인 — "UUV"라는 글자가 없어서

물 메쉬가 없는 심해 환경에서는, 수면을 대신할 가상 수위를 띄워 부력을 계산하는 fallback 경로가 있습니다. 문제는 이 fallback을 켤지 말지를 정하는 판정 조건이었습니다.

그 조건은 대략 이랬습니다 — "함대에 식별자에 UUV가 들어간 개체가 하나라도 있으면 수중 환경으로 보고 부력 fallback을 켠다."

도식 렌더링 중…

이날 함대에는 잠수함이 있었는데, 그 잠수함의 식별자에는 UUV라는 글자가 없었습니다. 잠수함은 분명 수중 개체지만, 식별자 명명 규칙상 다른 약어를 썼던 것입니다. 그래서 "수중 개체 없음"으로 잘못 판정 → 함대 전체의 부력 셋업이 통째로 skip → 7척 동반 침몰.

⚠️ substring 매칭을 게이트로 쓰는 함정"UUV" in 식별자 같은 문자열 포함 검사로 중요한 분기를 결정하면, 명명 규칙이 조금만 달라도 조용히 빗나갑니다. 그리고 그 한 번의 오판이 전체 블록을 끄기 때문에, 증상은 "배 한 척"이 아니라 "함대 전체"로 터집니다.

고침 — 그리고 교훈

해결은 판정 조건을 수상·수중을 포괄하는 식별 기준으로 넓히는 것이었습니다(USV·UUV·구축함·잠수함 등 해상 자산 키워드 + SUBMARINE 키워드 포함). 그 결과 z가 -2989m에서 +0.9m(수면 정착) 으로 돌아왔습니다 — 차트 오른쪽의 시안 곡선처럼요.

이 버그가 남긴 교훈은 세 가지입니다.

  1. deterministic 실패는 공통 분기를 의심하라 — 개별 값이 아니라 그들을 함께 처리하는 코드.
  2. substring/문자열 매칭으로 중요한 게이트를 만들지 마라 — 타입·플래그 같은 명시적 기준을 쓰라.
  3. "non-critical" 분기가 전체를 끌 수 있다 — 부력 한 줄이 함대 전체를 가라앉혔다.

한 줄 정리

📌 부력 fallback이 "UUV" 문자열 포함 하나로 켜졌는데, 잠수함 식별자엔 그 글자가 없어 함대 전체 부력이 통째로 꺼져 7척이 -2989m로 침몰했다. deterministic 실패는 공통 분기를, substring 게이트는 명시적 타입 기준으로 — 한 줄이 함대를 가라앉힐 수 있다.