Kaggle Growth 기록: Orbit Wars에서 첫 AI 봇 베이스라인 만들기

Kaggle Orbit Wars competition을 Kaggle Growth Lab의 agent 실험으로 정리하고, 단일 main.py 베이스라인 봇을 설계한 과정을 기록합니다.

Kaggle Growth 기록: Orbit Wars에서 첫 AI 봇 베이스라인 만들기

Kaggle Growth Lab은 단순히 Kaggle 점수를 올리는 프로젝트가 아니다. 내가 문제를 어떻게 읽고, 어떤 기준으로 첫 베이스라인을 만들고, 어디서 실패했는지 기록하는 실험장에 가깝다.

이번에 살펴본 대회는 Kaggle의 Orbit Wars다. 일반적인 표 데이터 예측 대회가 아니라, 2D 공간에서 행성을 점령하는 전략 봇을 만드는 simulation competition이다. Agent는 매 턴마다 현재 보드 상태를 보고, 어느 행성에서 어느 방향으로 몇 척의 함선을 보낼지 결정한다.

이 대회가 흥미로웠던 이유는 AI agent를 말로만 설명하지 않고 실제 환경에서 행동하게 만들 수 있기 때문이다. 관찰하고, 행동하고, 상대와 경쟁하고, replay를 보며 실패 원인을 찾고, 다음 버전의 전략을 고쳐야 한다.

왜 Orbit Wars를 골랐나

Kaggle Growth Lab에는 여러 트랙이 있다. Titanic 같은 기본 ML workflow도 있고, ROGII Wellbore처럼 정석적인 회귀 문제도 있고, ARC-AGI-3처럼 연구 성격이 강한 문제도 있다. 그중 Orbit Wars는 agentic AI 연습으로 적당한 중간 지점에 있었다.

Nemotron challenge처럼 큰 모델을 fine-tuning해야 하는 것도 아니고, ARC-AGI-3처럼 제출 구조부터 낯선 것도 아니다. Orbit Wars는 main.py 안에 agent(obs) 함수를 작성해서 제출할 수 있다. 즉, 첫 목표를 “강한 봇 만들기”가 아니라 “끝까지 제출 가능한 봇 만들기”로 잡을 수 있다.

내가 잡은 첫 목표는 작다.

  1. 대회 규칙을 읽고 핵심만 정리한다.
  2. 관찰값과 행동 형식을 이해한다.
  3. 가장 단순한 nearest-target baseline을 만든다.
  4. 로컬 validation을 통과시킨다.
  5. Kaggle에 첫 봇을 제출한다.
  6. replay와 log를 보고 다음 전략 하나를 고른다.

이 순서가 중요한 이유는 초보자가 처음부터 leaderboard를 보며 전략을 복잡하게 만들기 쉽기 때문이다. 하지만 agent 대회에서는 제출 형식, 시간 제한, 예외 처리, replay 해석이 먼저다. 봇이 강한지는 그다음 문제다.

대회 구조 간단히 이해하기

Orbit Wars는 태양을 중심으로 행성들이 배치된 100x100 연속 공간에서 진행된다. 플레이어는 자신이 가진 행성에서 함선을 보내 중립 행성이나 상대 행성을 점령한다.

관찰값에는 대략 다음 정보가 들어온다.

1
2
3
4
5
6
player: 현재 내 플레이어 id
planets: [id, owner, x, y, radius, ships, production]
fleets: [id, owner, x, y, angle, from_planet_id, ships]
angular_velocity: 안쪽 궤도 행성의 회전 속도
initial_planets: 초기 행성 위치
remainingOverageTime: 남은 초과 실행 시간

행동은 리스트로 반환한다.

1
[[from_planet_id, angle_in_radians, num_ships], ...]

아무 행동도 하지 않으려면 빈 리스트 []를 반환하면 된다. 이 구조가 꽤 직관적이다. “어느 행성에서, 어느 각도로, 몇 척을 보낼 것인가”만 결정하면 된다.

하지만 단순해 보이는 만큼 함정도 있다. 중앙에는 태양이 있고, 태양을 가로지르는 fleet은 파괴된다. 행성은 고정되어 있기도 하고 궤도를 따라 움직이기도 한다. 큰 fleet은 더 빠르게 움직인다. 여러 player가 동시에 행동하기 때문에 내가 보낸 함선이 도착할 때 보드 상황은 이미 달라져 있을 수 있다.

첫 베이스라인: 가까운 목표를 고르되, 태양은 피하기

공식 튜토리얼의 첫 baseline은 nearest target에 가깝다. 내 행성을 찾고, 내가 소유하지 않은 행성 중 가까운 곳으로 필요한 함선을 보낸다. 이 방식은 배우기 쉽지만 몇 가지 약점이 있다.

  • 이동 시간이 반영되지 않는다.
  • 여러 행성이 같은 목표를 동시에 공격할 수 있다.
  • 태양 충돌을 무시하면 함선이 사라진다.
  • 방어 병력을 남기지 않으면 쉽게 빼앗긴다.

그래서 Kaggle Growth Lab의 첫 src/main.py는 같은 정신을 유지하되, 작은 안전장치만 더했다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def agent(obs):
player = int(obs.get("player", 0))
planets = [_planet(row) for row in obs.get("planets", [])]

my_planets = [planet for planet in planets if planet["owner"] == player]
targets = [planet for planet in planets if planet["owner"] != player]

actions = []
for source in my_planets:
if source["ships"] < 15:
continue

target = _choose_target(source, targets)
if target is None:
continue

angle = _angle_between(source, target)
ships_to_send = ...
actions.append([source["id"], angle, ships_to_send])

return actions

핵심은 세 가지다.

첫째, 너무 적은 병력으로는 움직이지 않는다. 출발 행성에 최소한의 방어 병력을 남긴다.

둘째, 목표 점수는 거리와 생산량을 같이 본다. 가까운 행성이 항상 좋은 것은 아니다. 생산량이 높은 행성은 장기적으로 더 중요하다.

셋째, 태양을 가로지르는 경로는 피한다. 처음부터 완벽한 물리 시뮬레이션을 만들지는 않았지만, 선분과 태양 중심 사이의 거리를 계산해서 위험한 경로를 제외했다.

1
2
3
4
5
6
7
8
9
10
def _crosses_sun(source, target):
distance_to_sun = _segment_point_distance(
source["x"],
source["y"],
target["x"],
target["y"],
CENTER_X,
CENTER_Y,
)
return distance_to_sun <= SUN_RADIUS + SAFETY_MARGIN

이 정도면 강한 봇은 아니다. 하지만 첫 제출용으로는 충분히 의미가 있다. 제출 가능한 구조를 만들고, replay를 보며 다음 실험을 정할 수 있기 때문이다.

제출 전 체크리스트

agent competition에서는 코드를 작성하는 것만큼 제출 전 확인이 중요하다. 내 체크리스트는 다음과 같다.

1
2
3
4
5
6
7
8
9
10
- competition join
- rules accept
- kaggle-environments 버전 확인
- main.py 안에 agent(obs) 존재 확인
- random bot 상대로 local run
- self-play 실행
- timeout 또는 exception 없는지 확인
- 첫 제출 메시지는 nearest target baseline v1
- validation episode 통과 여부 기록
- replay/log 확인

로컬 검증 명령은 이런 형태로 잡았다.

1
python -c "from kaggle_environments import make; env = make('orbit_wars', debug=True); env.run(['competitions/orbit-wars/src/main.py', 'random']); print([(i, s.reward, s.status) for i, s in enumerate(env.steps[-1])])"

여기서 목표는 높은 점수를 확인하는 것이 아니다. 예외 없이 실행되는지, action format이 맞는지, evaluation 환경에서 바로 죽지 않는지 확인하는 것이다.

이번 실험에서 배운 점

이런 대회는 “모델을 학습시킨다”는 표현만으로는 설명이 부족하다. 더 정확히는 환경과 계약을 이해하는 일에 가깝다.

obs가 어떤 구조인지 알아야 하고, agent(obs)가 어떤 값을 반환해야 하는지 지켜야 한다. 시간이 오래 걸리면 안 되고, 외부 네트워크에 의존해서도 안 된다. replay를 보면 내가 생각한 전략과 실제 행동이 얼마나 다른지도 확인해야 한다.

Orbit Wars는 이 과정을 작게 연습하기 좋다.

  • 관찰: 현재 행성과 fleet 상태 읽기
  • 판단: 어떤 행성이 좋은 목표인지 계산하기
  • 행동: 각도와 함선 수를 반환하기
  • 검증: random/self-play로 죽지 않는지 확인하기
  • 회고: replay에서 실패한 행동을 찾아 다음 전략으로 만들기

이 흐름은 AI agent 개발에서도 그대로 반복된다. LLM을 붙이든, rule-based bot을 만들든, 중요한 것은 agent가 환경 안에서 실제로 어떤 행동을 했는지 확인하는 것이다.

다음 개선 후보

첫 baseline 이후에는 한 번에 많은 것을 고치지 않을 생각이다. 다음 실험은 하나씩만 바꾼다.

  1. 태양 회피 로직을 더 보수적으로 만들기
  2. 궤도 행성의 미래 위치를 예측하기
  3. enemy fleet이 다가오는 행성은 방어 우선순위를 높이기
  4. 여러 내 행성이 같은 target을 중복 공격하지 않게 하기
  5. 생산량 높은 행성을 초반에 더 적극적으로 점령하기

중요한 것은 모든 개선을 기록하는 것이다. 어떤 가설로 코드를 바꿨고, validation이나 replay에서 무엇이 달라졌는지 남겨야 한다. 그래야 점수가 오르지 않아도 배운 것이 남는다.

마무리

이번 Orbit Wars 참여 기록의 핵심은 “강한 봇”이 아니라 “작동하는 학습 루프”다.

Kaggle은 실험장이고, GitHub는 근거 저장소이며, 블로그는 배운 것을 다시 설명하는 공간이다. Orbit Wars는 이 세 가지를 연결하기 좋은 대회다. 작은 main.py 하나에서 시작하지만, 그 안에는 관찰, 행동, 검증, 회고라는 agent 개발의 기본 구조가 모두 들어 있다.

다음 기록에서는 실제 제출 결과와 replay에서 발견한 첫 실패 사례를 정리해볼 예정이다.

Comments

댓글

GitHub 계정으로 의견을 남길 수 있습니다. 댓글은 GitHub Discussions에 저장됩니다.