| Rank | Author | Strategy | Elo | Win% | Kill% | Attempts |
|---|---|---|---|---|---|---|
Two fighters face off in a 2D arena—1000 meters wide and 600 meters tall. The horizontal boundaries wrap around: fly off the right edge and you emerge from the left. But altitude is unforgiving. The ground (y ≤ 5) is instant death. The ceiling (y > 550) drains your speed, punishing fighters that try to escape upward. Everything happens at 120 Hz—10,800 ticks per 90-second match.
Your neural network controls a fighter plane. Every tick, it observes the battlefield—its own state, the opponent's relative position and velocity, and the positions of up to 8 bullets in flight—and decides three things: how hard to turn, how much throttle to apply, and whether to fire.
The goal is simple: eliminate your opponent or have more HP when the clock runs out. But the tactics are rich. Energy management matters—climbing costs speed, diving gains it. Rear-aspect armor means head-on attacks are risky and tail-chasing is inefficient. Damaged fighters get slower and turn worse, creating a snowball effect.
Train locally against four built-in opponents of increasing difficulty (dogfighter, chaser, ace, brawler), then upload your model as an ONNX file to compete against every other submission on the leaderboard.
When you submit a model, it plays 100 matches against every other submission on the leaderboard—50 random seeds, played from both sides (P0 and P1). Each match has one of four outcomes:
These results are converted to a score and then to an Elo rating:
Elo = 1000 + 400 × log10(score / (1 - score)) where score = (1.0 × elim_wins + 0.75 × hp_wins + 0.5 × draws) / total_matches
A score of 0.5 gives Elo 1000. Consistently winning pushes your rating above 1000; consistently losing drops it below. Decisive eliminations are rewarded more than narrow HP victories.
The simulation uses a gravity-based energy model. Gravity pulls at 130 m/s²—climbing bleeds speed, and diving converts altitude into velocity. This is the central tactical trade-off: altitude is potential energy you can spend later.
If your speed drops below 30 m/s, you stall. The nose drops, you lose control, and you can't recover until speed exceeds 40 m/s. Stalling near the ground is usually fatal. At the other extreme, the ceiling zone (above 550m) actively drains speed, preventing fighters from hiding at the top of the arena.
Damage compounds the problem. Each HP lost reduces your maximum speed by 3% and your turn rate by 2%. A fighter at 2 HP is 9% slower and 6% less maneuverable—often enough to seal the outcome.
Fighters have 5 HP and carry a gun that fires bullets at 400 m/s with an effective range of about 200 meters (bullets despawn after 0.5 seconds). The gun has a 0.75-second cooldown between shots, so every bullet counts.
Rear-aspect armor: bullets arriving within 45° of directly behind the target glance off and deal no damage. This means tail-chasing—the most intuitive attack angle—is actually the worst. The best attacks come from the side or from an oblique angle, where you can land hits while staying out of the defender's forward firing arc.
Effective combat requires combining maneuvering and shooting. You need to get into a firing position (close range, off the tail cone), maintain it long enough to fire, then disengage or reposition before the opponent can counter. Energy management determines whether you can execute these maneuvers.
Your model receives a 224-float observation vector—4 stacked frames of 56 floats each. Frame stacking gives temporal context: the model can infer velocities, accelerations, and trends from the differences between frames. The most recent frame occupies indices 168–223.
Each 56-float frame contains:
Self state [0..8): speed, cos(yaw), sin(yaw), hp, gun_cooldown, altitude, x_position, energy Opponent state [8..19): rel_x, rel_y, speed, cos(yaw), sin(yaw), hp, distance, closure_rate, angular_velocity, energy, angle_off_tail Bullets [19..51): 8 slots × 4 values each rel_x, rel_y, is_friendly, angle Relative geometry [51..55): angle_off_nose, opp_angle_off_nose, rel_vel_x, rel_vel_y Meta [55]: ticks_remaining
All values are normalized to roughly [-1, 1]. Positions are relative to the observing fighter. Bullet slots are zero-filled when fewer than 8 bullets are in flight.
Your model produces 3 continuous values:
Output shape: [1, 3]
[0] yaw_input — clamped to [-1, 1]
-1 = full left turn, +1 = full right turn
[1] throttle — clamped to [0, 1]
0 = idle, 1 = full thrust
[2] shoot — fires if > 0 (and gun is off cooldown)The outputs are treated as continuous controls, not discrete actions. Your model can output any float values—they will be clamped to the valid ranges. Subtle throttle and turn inputs enable precise maneuvering.