Your turret is surrounded. You do not aim with the mouse: your Python strategy controls every shot. Improve your code as the battle unfolds and keep your turret alive as long as possible.
Edit live, even mid-match: change your Python in the editor and press RUN to deploy it immediately. You do not need to wait for an upgrade break. Your health, enemies, score, upgrades, and elapsed time are preserved; if the code has a bug, the game pauses until you fix it and press RUN again.
Your mission
Implement decide(state). The game calls it 10 times per simulated second with a fresh dictionary describing the arena. Return one firing command, or None to wait:
return {"target": enemy["id"], "weapon": "rail"}
The runnable starter has deliberately bad targeting: it loops through living enemies in ID order, choosing a different target each decision and wrapping to the beginning. It always tries Pulse, ignoring distance, danger, weaknesses, energy, and cooldowns. This spreads damage and wastes decisions on unavailable shots. The last_target_id variable remembers its place between decisions and resets when you Run again. Replace this logic to improve target priority, weapon selection, energy efficiency, crowd control, and cooldown usage. There is no single perfect solution.
State reference
state contains:
time: elapsed simulated seconds.
wave: current wave, starting at 1.
hp: turret hull, initially 100.
max_hp: maximum hull, initially 100.
energy: current energy, initially 100.
max_energy: maximum energy, initially 100.
energy_regen: current regeneration per second, initially 18.
upgrades: dictionary of selected upgrade IDs and their stack counts.
kills: enemies destroyed.
enemies: list of currently living enemies, or [].
weapons: dictionary keyed by "pulse", "rail", "arc", and "cryo".
Each enemy contains id, kind, hp, max_hp, distance, speed, eta, damage, x, y, slowed, weakness, multipliers, and nearby.
eta estimates seconds until impact at the current speed. It does not predict when a cryo slow will expire.
nearby counts enemies within the current Arc hop range (initially 85 units), including the target. It is a clustering estimate, not a promise that all targets can be chained.
multipliers[weapon] is that enemy's damage multiplier for the named weapon.
- Coordinates are relative to the center turret, with positive x right and positive y down.
Each weapon contains its current upgraded damage, cost, range, base_cooldown (full cooldown duration), cooldown (seconds remaining), and ready. ready checks cooldown and available energy, but you must also check target range. Arc also exposes max_targets and hop_range; Cryo exposes blast_radius and slow_duration; Rail exposes line_half_width (added to each enemy's radius for hit detection).
Your arsenal
| Weapon | Base damage | Cooldown | Energy | Range | Special behavior |
|---|
pulse | 14 | 0.25 s | 4 | 260 | Cheap, rapid single-target fire |
rail | 68 | 1.4 s | 24 | 330 | Hits every enemy along its firing line, through to maximum range |
arc | 24 | 1.05 s | 20 | 240 | Chains to up to 4 enemies, each next hop within 85 units; damage ×0.85 per hop |
cryo | 9 | 1.8 s | 12 | 240 | Hits all enemies within 65 units of the target; slows them by 55% for 2.2 seconds |
Only one weapon can be selected per decision, but each has an independent cooldown. There is unlimited ammunition, not unlimited energy. Because decisions occur every 0.1 seconds, effective firing intervals round up to a decision tick. All attacks apply damage immediately; visible projectiles and beams are feedback, not extra travel time.
Rail can destroy a wave-one runner as soon as it spawns. A brief fading enemy silhouette marks the instant kill so you can still see what was hit; it is already eliminated, not a living target.
Know the enemy
The base stats (before wave scaling) and weapon damage multipliers are:
| Enemy | HP | Speed | Contact damage | Pulse | Rail | Arc | Cryo |
|---|
| Runner, pink triangle | 27 | 35 | 9 | 1.4× | 1× | 1× | 1× |
| Swarm, yellow diamond | 17 | 25 | 6 | 1× | 0.8× | 1.6× | 1.2× |
| Armored, orange square | 88 | 17 | 20 | 0.3× | 2× | 0.6× | 0.5× |
| Shielded, violet hexagon | 64 | 23 | 15 | 0.5× | 0.35× | 2.3× | 0.8× |
Swarm enemies arrive in groups of three. Cryo slows every type, including armored and shielded enemies. A hit removes an enemy from the arena and damages the turret; at zero hull, the attempt ends immediately.
Escalation and scoring
There is no final wave. Each wave lasts 20 seconds of active combat, then freezes for an upgrade choice. Remaining enemies stay in place and carry into the next wave. Newly spawned enemies gain 18% compounded HP per wave, 7% of their base speed per wave, and 8% of base contact damage per wave (rounded to an integer).
Wave 1 is a gentle introduction: six runners only, one every 3.5 seconds, starting at 0.5 seconds. Swarms join in wave 2, armored enemies in wave 3, and shielded enemies in wave 4. From wave 2, spawn rate starts at 0.5 groups/second and increases by 0.24 groups/second each wave. Each new wave's first group arrives 0.5 seconds after its announcement.
A large wave banner announces each transition and any newly introduced enemy type. The countdown shows when the next upgrade break begins. Health does not regenerate automatically, but repair upgrades can restore it. Your score is active survival time: time spent picking upgrades or editing code does not count. Best time is kept for the current mounted game session only; it is not a global leaderboard or permanent account record.
The enemy seed is 240926, so enemy spawns are repeatable. Upgrade offers use a separate randomly generated seed each new run. Changing your code during a break does not reroll the offers. Since different upgrades affect performance, compare both your code and your selected build when judging scores.
Select your upgrade
Survive a wave to receive three distinct random upgrade cards. Pick one card to apply it immediately and start the next wave. You cannot skip the choice, collect two cards, or reroll the cards by pressing Run. The arena, cooldowns, enemies, regeneration, and survival clock stay frozen until you choose.
There are 18 upgrade types:
| Upgrade | Effect per selection | Limit |
|---|
| Pulse / Rail / Arc / Cryo amplifier (4 types) | +15% damage for that weapon, multiplicatively | Unlimited |
| Pulse rapid cycle | −0.1 seconds cooldown, minimum 0.1 seconds | 2 |
| Rail rapid cycle | −0.2 seconds cooldown | 4 |
| Arc rapid cycle | −0.15 seconds cooldown | 4 |
| Cryo rapid cycle | −0.2 seconds cooldown | 5 |
| Reactor tuning | +3 energy regeneration per second | 6 |
| Expanded capacitor | +25 maximum energy and +25 current energy | 4 |
| Efficient discharge | All weapon energy costs ×0.92 | 5 |
| Reinforced hull | +15 maximum hull and repair 15 hull | 6 |
| Field repairs | Repair 35 hull, capped at maximum | Repeatable when damaged |
| Arc conductor | +1 maximum target, up to 8 | 4 |
| Extended discharge | +15 Arc hop range | 4 |
| Cryo dispersal | +12 Cryo blast radius | 4 |
| Deep freeze | +0.4 seconds Cryo slow duration | 4 |
| Wide-bore rail | +4 Rail line half-width | 4 |
Maxed-out upgrades are removed from the offer pool. Field repairs is not offered at full hull. Damage upgrades remain repeatable, so late waves still provide three choices. Open Upgrades in the game UI to inspect your build. The HUD and Python weapon stats reflect applied bonuses. Cooldown changes still obey the 0.1-second decision tick; faster cooldowns cannot create more than one command per decision.
Apply code without restarting
At any time, edit the normal Python editor and press the platform's Run button (the standalone preview labels it Apply strategy after the first run). Run never resets an existing attempt. During an upgrade break, choose Edit code first if you want to uncover the arena.
The game pauses while compiling and checking the new strategy, preserving enemies, time, health, energy, score, cooldowns, upgrades, and any offered cards. Successful code replaces your decision function immediately and resumes combat if it was running. Python globals reset when code is applied, but the game does not reset. A manually paused game stays paused until Resume; an upgrade break still requires one card selection.
If compilation, validation, or a later Python decision fails, a readable error appears and the game freezes until you fix the code and press Run again. Enemies cannot move or damage you, and time, cooldowns, energy regeneration, and upgrade selection remain frozen. A failed decision rolls back that simulation tick. Failed edits do not replace the last successfully applied strategy, but the game will not automatically fall back to it or let Resume bypass the error.
The engine cannot read unsaved editor changes automatically. Press Run before selecting the card if you want those changes to take effect. In the arena, Restart always starts a fresh attempt using the last successfully applied strategy and a fresh upgrade roll.
Run, inspect, improve
- Press Run to compile your Python and start the first attempt from full hull and energy.
- Watch the current decision, weapon cooldowns, energy bar, and incoming enemies.
- Use the in-game Pause, Stop, and Restart controls below SESSION BEST in the left HUD (along the bottom edge inside the game on narrow screens). Pause lets you inspect the battlefield. Stop freezes it; Run applies code and continues from that state (or returns to a pending upgrade choice).
- Change one part of your strategy and press Run to apply it without losing progress. Use Restart only when you want a fresh attempt. After the turret dies, Run can still apply code, but only Restart starts a new game.
Each weapon has its own sound, with additional cues for kills, hull damage, wave changes, and destruction. Use the page's built-in audio control to mute or unmute; the game follows that setting and has no separate sound button. If the browser blocks audio, unmute in the page UI and click Run or interact with the game to unlock playback.
The arena uses a 16:9 widescreen layout fitted to the available parent width and height. Controls and compact status panels stay inside the game; no toolbar below the canvas reduces the playing area. Simulation distances are unchanged.
If multiple code updates are submitted while Python is loading, only the latest is allowed to start or replace the strategy. Stop cancels pending updates. Background tabs pause simulation; long rendering stalls are clamped instead of suddenly advancing enemies. The standalone preview also has a 2× display-speed control; scores remain simulated seconds.
A syntactically valid but unavailable shot does not consume energy. The decision strip explains whether the weapon was cooling down, energy was insufficient, or the target was outside range. Invalid return values or Python errors pause the attempt with a readable message until corrected.
Imports and private-attribute access are disabled in this learning environment. Use supplied data and ordinary Python built-ins such as print, type, repr, len, min, max, and range. Normal loops are supported; a traced execution budget catches runaway Python loops. This is not a general-purpose hostile-code sandbox.
This is an endless optimization challenge, not a pass/fail exercise. It deliberately does not trigger a course-completion popup.
Start by selecting the enemy with the smallest eta, not merely the smallest distance. Then match weapons to weaknesses, check range and readiness, and avoid wasting high-damage shots on nearly defeated targets.