터널 안에서 날씨를 바꾸면 터널 제한속도가 사라짐

터널 안에서 Clear일 때 속도가 50 언저리도록 설정했는데 다른 날씨로 바꿨다가 다시 Clear로 돌아오면 속도가 70으로 변하는 오류 발견

-> 뭔가 덮어씌워지는 것 같았음

 

한 번 거쳐 가면 망가지는 패턴은 전형적인 상태 오염 신호

 

원인 추적 -> 두 시스템이 같은 변수를 만짐

터널 시스템: 터널 진입 시 MaxSpeed에 TunnelSpeedScale(0.7)을 곱한다
날씨 시스템: 날씨별로 MaxSpeed를 설정한다 (Clear 2200, Rain 1700, Snow 1400)

 

둘 다 같은 MaxSpeed 변수에 최종값을 씀 

1. 터널 진입 (Clear)
   MaxSpeed = 2200 × 0.7 = 1540   → 터널이라 느림 (속도 50)

2. 날씨를 Rain으로 (터널 안)
   ApplyWeatherProfile: MaxSpeed = 1700   ← 터널의 1540을 덮어씀

3. 날씨를 Clear로 복귀 (터널 안)
   ApplyWeatherProfile: MaxSpeed = 2200   ← 평지 속도로 복귀
   터널 안인데 MaxSpeed 2200 → 속도 70 (버그)

 

날씨 코드가 MaxSpeed를 설정할 때 터널의 적용 결과를 덮어씀

 

그리고 터널 코드의 베이스라인 캐싱은 play 통틀어 딱 한 번만 일어남

if (!bBaselineCached)          // 최초 1회만 true
{
    BaselineMaxSpeed = MaxSpeed;   // 최초 터널 진입 시점의 값에 영구 고정
    bBaselineCached = true;
}

 

터널의 기준값은 최초 터널 진입 때의 2200에 영구 고정됨

단순 덮어쓰기가 아니라 두 시스템이 서로의 변경을 모른 채 각자 다른 베이스라인을 1회 캐싱하는 이중 구조였음

 

베이스라인 캐싱은 누적 오염을 막는 좋은 패턴이었지만 두 시스템이 각자 캐싱하기 시작하면 그 자체가 새 문제가 되는구나

 

MaxSpeed는 항상 날씨 기준값 × 터널 배율로 계산

날씨가 바뀌든 터널이 바뀌든 둘 다 이 공식으로 다시 계산함

핵심은 순서 독립성

누가 먼저 호출되든 같은 결과로 수렴해야 한다

날씨는 "순수 날씨 기준값"만 별도 변수(WeatherBaseMaxSpeed)에 저장
터널 상태를 기억하는 변수(bIsInTunnelNow) 도입
최종: MaxSpeed = WeatherBaseMaxSpeed × (터널 안이면 0.7)
이 합성을 날씨 콜백과 터널 콜백 양쪽 모두에서 수행

 

 

새로운 문제 발견

터널 안에서 비/눈으로 바꾸면 속도가 더 줄어듦

근데 터널 안에서 날씨는 의미가 없음! 

터널 안에서 날씨가 속도를 가르는 건 현실과 맞지 않음 -> 설정 오류

 

날씨가 건드리는 건 MaxSpeed만이 아니고 LateralFriction, DecelRate, MinSpeed, BrakePreviewDist도 전부 날씨별로 바뀜

터널 안 = 날씨가 Clear인 것처럼 취급하는 게 제일 깔끔하다고 판단!

 

EffectiveWeather 생성

void ApplyWeatherProfile(EWeather Weather)
{
    CurrentWeather = Weather;   // 실제 날씨 기억 (터널 나갈 때 복귀용)

    // 터널 안이면 날씨를 Clear로 간주 — 이 한 줄이 핵심
    const EWeather EffectiveWeather = bIsInTunnelNow ? EWeather::Clear : Weather;

    // 이하 모든 switch는 EffectiveWeather를 본다
}

 

분기는 함수 입구 한 곳

그 아래 모든 계산(마찰/속도/감속/Min/Preview)은 EffectiveWeather 하나만 봄

터널 안이면 EffectiveWeather가 Clear니까 전부 자동으로 Clear 처리됨

 

마지막으로 터널 진입/이탈 시 유효 날씨가 바뀐 셈이므로 OnTunnelToggled 끝에서 ApplyWeatherProfile(CurrentWeather)를 다시 호출하게 했음

터널에 들어가면 실제 날씨가 눈이어도 EffectiveWeather=Clear로 재계산되고, 나오면 기억해둔 CurrentWeather(눈)로 복원됨

 

버그 수정

 

알게된 것

1. 덮어쓰기 오류 없애는 건 더 잘 덮어쓰기가 아니라 덮어쓰기 없애기

 

각 시스템이 변수에 직접 값을 박는 게 아니라 내 기여분을 곱해서 합치는 방식으로 바꾸기

// 덮어쓰기 방식 (나쁨)
MaxSpeed = 120;   // 도로
MaxSpeed = 80;    // 날씨

// 곱셈 합성 (좋음)
MaxSpeed = BaseSpeed * RoadFactor * WeatherFactor;
// 도로: RoadFactor = 1.2
// 날씨: WeatherFactor = 0.67

이러면 도로가 먼저 계산되든 날씨가 먼저든 최종 곱셈 결과는 같음

-> 순서 독립성(commutativity)

 

2. 분기는 한 점에 모으기

같은 조건 판단(터널이면 Clear)을 다섯 군데 흩으면 다섯 개의 잠재 버그가 될 수 있음

입구에서 한 번 판단하고 그 결과를 아래가 따르게 하면 모순이 구조적으로 불가능해짐

// 한 곳에서 결정
WeatherContext currentWeather = isInTunnel ? Clear : ActualWeather;

// 아래 시스템들은 currentWeather를 그냥 따른다
friction = ComputeFriction(currentWeather);
visibility = ComputeVisibility(currentWeather);
maxSpeed = ComputeMaxSpeed(currentWeather);

 

 

3. 좋은 코드가 항상 좋은 건 아님

// 처음 한 번만 원본 값 저장
if (!cachedBaseline) {
    baselineMaxSpeed = MaxSpeed;
    cachedBaseline = true;
}
// 이후엔 캐시된 값에서 계산
MaxSpeed = baselineMaxSpeed * weatherFactor;

 

베이스라인 캐싱은 좋은 패턴이지만 두 시스템이 각자 한 번씩 캐싱하면 문제가 됨

 

날씨 시스템이 t=0에 캐싱: baselineMaxSpeed = 100 (당시 원본)
그 직후 도로 시스템이 t=0.1에 들어옴: baselineMaxSpeed = 80 (날씨가 이미 한 번 만진 후의 값)

 

즉, 베이스라인 캐싱은 분명 누적 오염을 막는 좋은 패턴이지만 특정 부분에선 아닐 수 있음

패턴의 안전성은 그 패턴이 사용되는 경계에 따라 달라질 수 있다

+ Recent posts