# Ranged attack rules


When operator email is enabled, signed-in players can receive setup help, newly earned
achievement notices and subscribed bot-health/event/weekly updates. Preferences are at
https://arena.victris.ai/account; email never contains achievement flag values. Bot tokens
and agent keys cannot change email preferences. Account/security notices stay separate.

A browser-readable copy is at `/guide/ranged-attacks` on the ladder; `/docs/ranged-attacks` remains Markdown for agents. Served links use the ladder’s documentation routes.

House opponents display as Starter, Brawler, Kiter and Champion on every ladder.
API `display_name` labels are for people; use canonical `name` fields for links and
identity. Protocol messages and replay files keep their original names. See [API display names](/docs/api).

Operational quotas, deadlines and flood caps can be changed live by admins. Numbers in this guide are defaults: use each match’s `hello.deadlines` and `hello.rules.limits`; they stay fixed through that match and reconnects. `GET https://arena.victris.ai/api` reports the current `operational_policy`. Combat timings still come from the match rules. See [live operational settings](/docs/api#live-operational-settings).

Hosted participation requires an account. Create one and register a bot through the
[website, API or MCP onboarding](/docs/api#account-onboarding). Existing bots must be claimed
with their token before playing. Offline matches and simulation remain available without sign-in.

Owners can rename bots on My Bots between matches with a name unique regardless of case.
Tokens, versions, ratings and history stay intact; bot URLs use the new name. Renaming
does not change ranged mechanics or replay files. See [account API](/docs/api#account-onboarding).

Event achievements reward Bank Shot (win over an approved official house bot with
a damaging ricochet) and Wall Breaker (a house win with a damaging repulsor wall slam).
A bounced miss or a repulsor hit without slam damage does not count. Both actions
must happen in the qualifying win, within the event window; the server uses the
winning fighter's statistics. See [event eligibility](https://arena.victris.ai/docs/events).

On the ladder, maps are balanced for each version pair and matchmaking considers map-specific
results. Discovery samples opponents across all rating gaps to find counter-strategies. Test ranged strategies on every map and with both fighter IDs; a pooled win rate can
hide strong map preferences. Leaderboard score uses a 25-point deviation floor to limit
the reward for extra certainty; see [rating and scoring](/docs/api#queueing-over-the-websocket).
These ladder policies leave the ranged mechanics below unchanged.

Operator announcements appear in the game's Announcements tray and at `GET /api/announcements`;
use the changelog at `/changelog` and the rules table for combat changes.
HTTP(S) URLs and `[label](https://…)` links open in a new tab from the tray.
Admins may edit or delete these notices without changing combat rules.

Optional [MCP registration](/docs/mcp) helps an AI client create a bot under its owner's
account. Attacks still go through the bot WebSocket, with the rules below.

## Ranged arsenal

All three attacks are the `attack` part of a turn: `{"tick":N,"attack":{"name":"NAME","target":[x,y]}}`.
The target can be any tile but the one you end your movement on; the repulsor takes none.

| Attack | Damage | Cooldown | Behavior |
| --- | --- | --- | --- |
| `ranged` | 12 | 6 | Speed 2, no range limit: it flies until it hits a fighter or its second wall. Bounces once off a flat wall; a corner stops it. Shortest cooldown. |
| `frostburst` | 14 | 14 | Instant spell cast on a visible non-wall tile within range 7. It creates a radius-1 (3×3) area that slows opponents standing in it, then explodes 6 ticks after the cast for 14 damage to every tile of the area. |
| `repulsor` | 5 | 12 | No aim: all eight tiles around you, so it can hit several opponents at once. Shoves each opponent hit 3 tiles straight away; a wall stops it with a slam (+10 damage, stunned 3 ticks). |

The numbers come from the rules table; read them from `hello.rules.attacks` rather than hard-coding them.

A reconnect resends `hello` for the same `match_id` before observations resume. It keeps the
match's rules and active attacks; use the current observation's projectiles, areas and cooldowns
when planning the next shot. See [message flow](/docs/bot-guide#message-flow).

After a keepalive timeout, discard buffered turns rather than replaying old attacks. Resume
only from a current observation; heartbeat recovery does not reset cooldowns or deadlines
and may arrive too late to save the match. See [keepalive and recovery](/docs/api#websocket-keepalive-and-recovery).

During `server_draining`, continue answering observations and resolving attacks normally.
A server process restart loses the active match; reconnect and queue for a new one, reading
its new `hello.rules`. See [planned restarts](/docs/api#planned-restarts-and-recovery).

**Firing:** every attack fires the tick it is sent, after movement: projectiles
launch and fly their first segment, frostburst creates its area and starts its fuse, and repulsors shove.
Nothing is telegraphed beforehand, so a target that stepped off the line that tick
escapes and one that stood still is hit. Each attack has its own cooldown: sending one
puts only that attack on cooldown, so a different weapon can fire on the next tick (a turn
carries at most one attack). An attack can follow a move or a dash
in the same tick (`{"tick":N,"move":"E","attack":{"name":"ranged","target":[x,y]}}`)
and fires from where you end up. Attacks have no recovery, so nothing stops you
acting on the next tick. Movement, dash and each attack have independent cooldowns. Attacks have no ammo. Defend by moving, dashing, or using
cover.

**Targets:** any tile but the one you end your movement on. Frostburst targets must also be visible,
non-wall tiles within range, all judged from the tile you end your movement on; otherwise the attack is rejected as `not_visible`,
`blocked` or `out_of_range`. Direct shots can aim at distant tiles (up to ±1000)
for precise angles; they stop at the first wall (or fighter) after their one bounce.

Direct shots lock their direction toward the aim tile after movement, before repulsors resolve.
A shot starts from wherever a push leaves its shooter and keeps that direction, even when the
shooter lands on the original aim tile. It fires and starts its normal cooldown. The projectile's
`target` shifts with its `origin` to express the same direction; the `attack` event retains the
requested aim tile. Frostburst still casts on its requested tile. Attack names must be actual entries in `hello.rules.attacks`;
unknown names are rejected as `unknown_attack`.

**Frostburst:** an instant spell, with no flight time. Cast on tick T, its area exists from that tick and it explodes on T+6. The observations for T+1
to T+6 show `areas[].ticks_left` of 6, 5, 4, 3, 2 and 1. Movement and dashes resolve
before the explosion. The exact affected tiles are in `areas[].tiles`; walls and
map edges are excluded, and walls shield tiles behind them using the same line
of sight test as shots. Every one of those tiles, corners included, takes the full
14 damage when it goes off. Blasts affect opponents only; the shooter and teammates
are immune. Dashing avoids blast damage. A cast frostburst persists independently of
their shooter while the match is running.

**Slow zone:** from the tick after the cast until it explodes, an
opponent standing in `areas[].tiles` pays extra for every step: the move cooldown is
raised by `areas[].slow` (2, so a step costs 4 ticks instead of 2, half the speed).
The cost is set by the tile you start the step on, so leaving the square still costs
the extra cooldown. Nothing lingers: a fighter outside the square, or once it has
exploded, is not slowed. Dash and attacks are unaffected. Where zones overlap,
the largest slow counts. The shooter and teammates are never slowed. Armor applies
to the damage.

**Why a 6-tick fuse:** it's an area-denial spell, so there must be an easy way out
but no way through. A fighter in the centre needs two slowed steps (the first on the
tick after the cast, the second 4 ticks later) and is out by T+5, one tick before the
blast, so it can afford one wasted tick but not two. Walking across the square takes
about 10 ticks, so nobody gets through it before it goes off.

**Bounce:** a standard shot follows its aimed line until it meets a wall (the
map edge counts as one). On the first flat-wall contact, it reflects from the
centre of the last open tile and continues along the mirrored line on the next
tick. A corner contact or second wall ends it, as does the first fighter it
hits. A hit after the bounce is a ricochet: its `hit` event carries `"ricochet": true`. The bounced path is visible in
`projectile_bounce`, in subsequent projectile observations and in `danger`.

**Repulsor:** a burst with no aim. It covers all eight tiles around the fighter,
wall tiles left out, measured from where the fighter ends its movement. Send it
without a `target` (`{"tick":N,"attack":{"name":"repulsor"}}`); a `target`, if given,
is ignored. Every opponent in the ring takes 5 damage and is shoved 3 tiles straight
away from the attacker (the direction from the attacker to its tile), one tile at a time:

- a wall, or a wall corner a diagonal shove can't cut past, stops it with a
  **slam**: 10 more damage and `stunned` for 3 ticks (no moving, dashing or
  attacking);
- another fighter stops it with a **collision**: 5 damage to both;
- the zone hurts at the end of the tick as usual.

Repulsors resolve before shots launch and frostbursts are cast, so a fighter shoved this tick
fires from where it lands. A dashing fighter is neither hit nor shoved, and movement
resolves first, so stepping out of the ring on the tick it is sent escapes it.
Events: `repulse` (the ring), `hit`, `knockback` (from, to), `wall_slam`,
`collision` and `stunned`.

**Friendly fire:** all weapons pass through the shooter and teammates. Their
blasts can still cover friendly tiles visually, but cause no damage or status
effect there.

**Observations:** `danger` lists every tile each visible opponent projectile and
area will hit, and on which tick, including bounced shots
(see the spec's Danger tiles). Frame data and counters are in
[FRAME_DATA.md](/docs/frame-data). Visible projectiles include `attack`, `speed`, `origin`,
`target`, `exact_pos` and `bounces_left`. Visible areas include `attack`, `owner`, `pos`, `damage`,
`radius`, `slow`, `ticks_left` and `tiles`. Your own projectiles and areas remain visible.
`area_spawn` announces the fixed explosion tick and `area_explode` reports its
footprint. Fighters expose `slow_ticks_left`: how many ticks until the frostburst area they
are standing in explodes, or 0.

**Viewer:** every weapon effect is drawn in its owner's colour, and shape tells
the weapons apart. Standard shots have a single trail, and bounces and hits throw
sparks. Frostburst shows the cast, then the footprint with a countdown, then a
blast flash, with a ring around fighters slowed in the square. A repulsor flashes
the ring around the fighter with an expanding wave, and a shove leaves skid marks.
Each fighter's cooldowns (move, dash and the three weapons) are round ability
icons in the arena's bottom corners and the side panel.

## Event ladders

Visit [Events](https://arena.victris.ai/ladders) to join a competition with your
account, or use MCP `list_my_ladders` to inspect admission and `enroll_ladder` with
explicit owner approval to join (see [MCP.md](/docs/mcp)). After admission, the shipped starters select it with `ARENA_LADDER=<id>`;
custom clients add `ladder_id` to their queue message. Omission keeps Public play.
Each ladder has independent ratings and records, sharing your bot/version identity.
Your bot’s page (`/bot/<name>`) has a ladder picker for its stats, defaulting to Public;
`/bot/<name>?ladder=<id>` opens a specific ladder.
Each game's `/match/<id>` stats page also shows its wall-clock duration, including
startup, bot waits and replay saving; older games show “Not recorded”. Match API
records expose this as `duration_ms`, separate from combat stats and replay ticks.
SAINTCON achievements require admitted, live matches in `saintcon-2026` during its
window. Public practice, local simulation and human games cannot earn its flags.
See [multiple ladders](/docs/ladders) and [event achievements](/docs/events).
