저는 TORCS 설치에 1.5일을 보냈고, 제 AI 드라이버에게 **Max Verstoppin'**이라는 이름을 붙여주었으며, 그가 즉시 벽에 전속력으로 돌진하는 것을 지켜보았습니다. 이것은 IBM Bob과 제가 그를 Max Verstappen으로 변화시킨 이야기입니다.
도전 과제 (The Challenge)
IBM Bob TORCS University Challenge는 기본 Python 스크립트인 snakeoil3_gym.py를 제공합니다. 이 스크립트는 오픈 소스 레이싱 시뮬레이터인 TORCS의 AI 자동차를 제어합니다. 목표는 IBM Bob을 크루로 활용하여 스크립트를 개선하고, 정지 상태에서 출발하여 손상 없이 Corkscrew 트랙에서 가장 빠른 클린 랩 기록을 제출하는 것입니다.
간단한 개요. 치명적인 실행력.
알파 — 기준점 (Baseline) (DNF, 1,863 데미지)
기본 drive_example() 함수는 다음과 같았습니다:
target_speed = 300
R['steer'] = S['angle'] * 15 / PI
...
브레이크 없음. 코너 인지 능력 없음. 바퀴를 심하게 흔들리게 만드는 15의 조향 승수(steering multiplier). 앞에 무엇이 있든 상관없이 300 km/h의 목표 속도.
Max는 237 km/h에 도달했지만, 첫 번째 코너를 완전히 놓쳤고, 서버가 100,000 단계 제한에 도달할 때까지 가드레일에 끼어 있었습니다.
랩: 0. 데미지: 1,863. 최고 속도: 237 km/h.
IBM Bob 투입하기
저는 스크립트를 IBM Bob에게 보여주고 무엇이 잘못되었는지 분석해 달라고 요청했습니다. 그것은 drive_example()을 분해하고 즉시 세 가지 핵심 문제를 지적했습니다:
- 제동 시스템 부재 — 액션 딕셔너리(action dictionary)의
brake 키가 한 번도 사용되지 않음
- 조향 승수 과격함 —
* 15가 심각한 진동을 유발함
- 맹목적인 목표 속도 — 앞에 무엇이 있는지 전혀 인지하지 못한 채 300 km/h 유지
문제들을 이해하자 접근 방식은 간단했습니다: 한 번에 하나 또는 두 가지를 수정하고, 한 바퀴를 달리고, 원격 측정(telemetry) 데이터를 읽어 Bob에게 다시 공급하여 다음 병목 지점을 파악하는 것입니다. 8번의 반복. 알파부터 이오타까지.
베타 — 첫 클린 랩 (02:24.17, 0 데미지)
첫 번째 수정 사항들:
- 전방 트랙 센서를 사용하여 비례 제동(proportional braking)을 추가하여 코너를 감지하도록 함
- 스티어링 승수(steering multiplier)를
15에서 10으로 줄임
- 하드코딩된 300 대신,
track[8], track[9], track[10] (전방을 향하는 센서)를 사용하여 코너 인지 목표 속도(corner-aware target speed)를 계산하도록 함
forward_dist = min(track[8], track[9], track[10])
target_speed = max(60, min(220, forward_dist * 1.1))
...
Max가 사상 첫 클린 랩을 기록했습니다. 02:24.17. 데미지 없음.
그는 느렸습니다 — 1.1 승수는 100m 직선 구간 센서 판독값으로 목표 속도를 단지 110 km/h로 설정했을 뿐입니다 — 하지만 그는 클린했습니다. 기초가 다져졌습니다.
Gamma → Epsilon — 페이스 향상 (02:15 → 02:06)
다음 세 번의 실행 동안, 승수는 점진적으로 상향 조정되었습니다:
| Run | Multiplier | Lap Time | Top Speed |
|---|
| Gamma | × 2.0 | 02:15.40 | 217 km/h |
| Epsilon | × 2.5 | 02:06.71 | 225 km/h |
각 단계마다 제동 시스템이 데미지를 '0'으로 유지하는 동안, 더 많은 직선 속도가 확보되었습니다. 또한 브레이크 램프 분모(brake ramp divisor)도 조정되어 고속에서 코너에 접근할 때 차량의 감속을 더 빠르게 만들었습니다.
Zeta — 핵심 아키텍처 통찰 (02:04.90)
이것은 프로젝트 전체에서 가장 중요한 변경 사항이었습니다.
센서 로직은 5개 센서 전방 원뿔(forward cone)에 걸쳐 min()을 사용하여 목표 속도를 설정하고 있었습니다. 문제는 Max가 트랙 가장자리로 드리프트할 때, 이 원뿔 내의 대각선 센서들이 경계 벽을 향해 짧은 판독값 — 때로는 20~30m — 을 반환한다는 것이었습니다. 이는 완전히 열린 직선 구간에서 발생했습니다. 차량은 코너가 온 것으로 착각하고 아무 이유 없이 스로틀을 줄였습니다.
수정된 방법은 센서 판독값을 두 개의 별도 신호로 분리하는 것이었습니다:
# 원뿔의 MAX → 직선 속도 (하나의 명확한 센서만으로 충분)
straight_dist = max(track[7], track[8], track[9], track[10], track[11])
...
어떤 전방 센서라도 깨끗한 도로를 감지하는 한, Max는 속도를 냅니다. 코너 제동은 오직 바로 앞의 도로가 실제로 좁아질 때만 작동합니다.
02:04.90. 228 km/h. 0 damage.
Eta — 보정의 교훈 (A Lesson in Calibration) (02:11.21 — 회귀)
모든 주행이 순조롭지는 않았다. 코너 감지 임계값(corner detection threshold)을 < 45m로 낮춘 것(기존 < 60m)은 마지막 급커브에서 제동을 너무 오래 지연시켰다. Max는 트랙 바깥쪽으로 넓게 나갔다가 접지력을 잃었고, 피해가 전혀 없음에도 불구하고 랩 타임이 6초나 올라갔다.
트랙 이탈(off-track excursion)은 TORCS에서 피해로 기록되지 않았다. 잔디밭은 단순히 속도를 떨어뜨릴 뿐이다. 이는 지표(metrics)가 항상 전체 이야기를 들려주는 것은 아니라는 좋은 상기시켜 주는 것이었다.
Theta → Iota — 2분 미만 돌파 (Breaking Sub-2:00) (02:02 → 01:58.15)
Theta는 절충안을 제시했다: 임계값 < 55m, 승수(multiplier) 2.8. 새로운 PB(Personal Best)를 02:02.42로 기록했다.
Iota는 남아있던 마지막 문제를 해결했다. Max가 코너 탈출 후 트랙 가장자리를 따라 서성거려 직선 구간에서 스로틀을 끝까지 밟지 못하는 문제였다. 두 가지 원인이 있었다:
- 중앙선에서 벗어났을 때(off-centre), 대각선 센서들이 여전히 벽을 감지하여
straight_dist를 억제했다. 수정: |trackPos| > 0.5일 때만 전방 직진 센서(dead-ahead sensor)로 되돌아간다.
- 에이펙스 스로틀 부스트는
|steer| < 0.1을 요구했는데, 이는 너무 타이트했다. Max가 중앙으로 돌아오면서 유지했던 완만한 수정은 결코 자격을 얻지 못했다. 이를 0.2로 완화했다.
- 트랙 가장자리에 가까워질 때(near the edge) 중앙 정렬 게인(Centering gain)이 두 배가 되었다(
0.15 → 0.30) 덕분에 그가 레이싱 라인으로 더 빠르게 돌아올 수 있었다.
track_pos = S.get('trackPos', 0)
if abs(track_pos) > 0.5:
straight_dist = track[9] # 전방 직진 센서만 사용 — 대각선 센서 무시
...
01:58.15. 235 km/h. 0 damage. Sub-2:00.
최종 발전 과정 (Final Progression)
| Run | Lap Time | Top Speed | Damage | Key Change |
|---|
| Alpha | DNF | 237 km/h | 1,863 | Baseline — 블라인드 스로틀, 제동 없음 |
| ... | | | | |
내가 배운 것 (What I Learned)
IBM Bob이 한 가장 유용한 일은 코드를 작성한 것이 아니었다. 무언가를 변경하기 전에 코드가 실제로 무엇을 하고 있는지 이해하도록 도와준 것이었다. Max가 왜 충돌했는지(제동 없음, 블라인드 속도) 또는 느려졌는지(센서가 직선 구간에서 벽을 감지하는 것)를 정확히 볼 수 있게 되자, 각 수정 사항이 명확해졌다.
반복 루프 — 실행, 관찰, 진단, 수정 — 역시 좋은 엔지니어링 그 자체입니다. 모든 회귀(Eta)는 무언가를 가르쳐 주었습니다. 랩 타임, 손상도, 그리고 최고 속도가 함께 제시하는 정보가 어느 하나의 수치만으로는 얻을 수 없는 더 완전한 이야기를 들려주었습니다.
이 프로젝트를 진행하면서 IBM SkillsBuild에서 IBM Granite Models for Software Development 과정을 이수하고 공식 배지를 받았습니다. 이는 Bob과 센서 배열 및 제동 로직에 대해 기술적인 논의를 할 때, 이러한 모델들이 코드를 어떻게 추론하는지에 대한 확실한 배경 지식을 제공해 주었습니다.
링크