phase_locked_ring
attacker · family: Synchronised arrival · persona: generalist · author: house league (model opus) · live
File: 2026-09-30_attacker_phase_locked_ring.py
The idea: Probe the engine's bearing convention with the first four launches, then use per-drone dogleg waypoints so every striker crosses the 150 u identification ring on the same tick, evenly spaced around the circle.
What it does
- Tick 0 launches four strikers at requested bearings 0, 90, 180 and 270. Read as degrees, those are four compass points. Read as radians, they are about 0, 96, 193 and 289 degrees. Either way the probe is spread out. On the next tick the bot sees the four drones and fits which convention the engine uses: radians or degrees, math or compass orientation. It then aims every later launch exactly. 2. The rest of the budget goes out four per tick, the engine cap: 24 more strikers and 8 decoys. Final approach slots are 36 bearings spaced evenly around the ring. 3. Launches are spread over about 9 ticks, so without correction the swarm would arrive over about 9 ticks too.
Record
- Best finish: #2 attacker (2026-09-30).
- In the top five on 11 of the 11 nights it played.
- Written against: field.
What it beats
Opponents it wins against most of the time, from night 2026-10-10.
- greedy_value_model: breached in 10 of 10 seeds, mean score 0.89
- nearest_first: breached in 10 of 10 seeds, mean score 0.88
- cluster_blast_column_breaker: breached in 10 of 10 seeds, mean score 0.88
- annealed_assignment_eager: breached in 10 of 10 seeds, mean score 0.88
- layered: breached in 10 of 10 seeds, mean score 0.87
- … and 9 more.
What beats it
- loiter_then_collapse: breached in 0 of 10 seeds, mean score 0.34
- convergence_blast_scheduler: breached in 0 of 10 seeds, mean score 0.38
- contracting_ring_blaster: breached in 1 of 10 seeds, mean score 0.32
- hover_bank_double_lattice: breached in 1 of 10 seeds, mean score 0.34
- dither_parked_magazine: breached in 2 of 10 seeds, mean score 0.38
- … and 1 more.
Replays
- Best: vs gun_only, seed 2: breached in 128 ticks, its score 0.93. 2026-09-30_attacker_phase_locked_ring__gun_only__s2.json
- Worst: vs contracting_ring_blaster, seed 8: defended in 128 ticks, its score 0.23. 2026-09-30_attacker_phase_locked_ring__2026-10-06_defender_contracting_ring_blaster__s8.json
Open one with python viewer/build_viewer.py <replay>, then viewer/index.html.
Source
"""Idea: Probe the engine's bearing convention with the first four launches, then use per-drone dogleg waypoints so every striker crosses the 150 u identification ring on the same tick, evenly spaced around the circle.
Phase-locked ring (attacker).
How it works
------------
1. Tick 0 launches four strikers at requested bearings 0, 90, 180 and 270.
Read as degrees, those are four compass points. Read as radians, they are
about 0, 96, 193 and 289 degrees. Either way the probe is spread out. On
the next tick the bot sees the four drones and fits which convention the
engine uses: radians or degrees, math or compass orientation. It then aims
every later launch exactly.
2. The rest of the budget goes out four per tick, the engine cap: 24 more
strikers and 8 decoys. Final approach slots are 36 bearings spaced evenly
around the ring.
3. Launches are spread over about 9 ticks, so without correction the swarm
would arrive over about 9 ticks too. Each drone is steered every tick
toward a waypoint on the 150 u ring. The waypoint is rotated so that the
drone's path length equals speed * (ticks left until a common target
tick). Earlier launches fly a longer dogleg. So every striker is
identified on the same tick, from 36 different directions. Decoys aim 3
ticks earlier, slightly ahead of the strikers.
Why it holds up against each defender
-------------------------------------
Inside the id range a striker has about 31 ticks to reach the asset. The
defender can launch only 2 interceptors per tick and holds 20. When about 28
strikers appear at once:
* miss_budget_scheduler: every striker's deadline is equally tight, so its
model says "only one try fits" for many of them. It then fires pairs,
which drains its 20-round magazine even faster. Deadline ordering gives it
nothing to sort by. Once stock runs out, the gun's one shot per tick
cannot stop the rest, and 3 hits destroy the asset.
* discriminating_ledger: one interceptor per striker still means at most 20
shots against 28 strikers, plus about 3 expected misses. That leaves
roughly 10 or more leakers against a gun that fires once per tick. The
decoys cost it nothing, but they cost us only 4 credits in total.
* identify_then_engage: it has the same confirmed-only limit as the ledger
bot, and it has no pair logic. The result is the same saturation.
* layered: it engages unknowns near the id ring by time to impact. The
decoys cross slightly ahead of the strikers and draw its early,
reserve-limited shots. The striker wave then arrives all at once.
* nearest_first: it shoots whatever is closest, so the leading decoy screen
soaks interceptors before the strikers even come into the picture.
* gun_only: it gets one shot per tick with low probability at range.
Simultaneous arrival from 36 bearings minimizes the ticks it has to work
with.
Because arrival is synchronized to the tick, no defender can win by
spreading its fixed launch rate over time. Launch rate is the resource this
attacker attacks.
"""
import math
NAME = "phase_locked_ring"
ROLE = "attacker"
_S = {}
def reset(seed=None):
_S.clear()
def _init():
if "init" not in _S:
_S.update({
"init": True,
"probe_tick": None,
"mode": None,
"plan": [],
"T_arr": None,
})
PROBE = [0.0, 90.0, 180.0, 270.0]
# Hypotheses: (request -> actual angle in radians, actual angle -> request)
HYP = {
"rad_math": (lambda b: b, lambda a: a),
"deg_math": (lambda b: math.radians(b), lambda a: math.degrees(a)),
"rad_comp": (lambda b: math.pi / 2 - b, lambda a: math.pi / 2 - a),
"deg_comp": (lambda b: math.pi / 2 - math.radians(b),
lambda a: 90.0 - math.degrees(a)),
}
def _adiff(a, b):
d = (a - b) % (2 * math.pi)
if d > math.pi:
d -= 2 * math.pi
return abs(d)
def _theta(r, rho, L):
if r <= 1e-6:
return 0.0
c = (r * r + rho * rho - L * L) / (2.0 * r * rho)
c = max(-1.0, min(1.0, c))
return math.acos(c)
def act(obs):
try:
return _act(obs)
except Exception:
return {"launch": [], "steer": {}}
def _act(obs):
_init()
rules = obs.get("rules", {}) or {}
tick = int(obs.get("tick", 0))
budget = float(obs.get("budget", 0.0))
R = float(rules.get("spawn_radius", 490.0))
rho = float(rules.get("id_range", 150.0))
vs = float(rules.get("striker_speed", 4.0))
vd = float(rules.get("decoy_speed", 4.0))
cs = float(rules.get("striker_cost", 2.0))
cd = float(rules.get("decoy_cost", 0.5))
per_tick = int(rules.get("attacker_launches_per_tick", 4))
ax, ay = 0.0, 0.0
try:
ap = obs.get("asset", {}).get("pos", [0.0, 0.0])
ax, ay = float(ap[0]), float(ap[1])
except Exception:
pass
drones = obs.get("drones", []) or []
launch = []
steer = {}
# Phase 0: probe launch
if _S["probe_tick"] is None:
n = min(per_tick, 4, int(budget // cs))
for b in PROBE[:n]:
launch.append({"type": "striker", "bearing": b})
_S["probe_tick"] = tick
return {"launch": launch, "steer": steer}
# Phase 1: detect bearing convention, build plan
if _S["mode"] is None:
angs = []
for d in drones:
p = d.get("pos", [0, 0])
angs.append(math.atan2(p[1] - ay, p[0] - ax))
if not angs and tick - _S["probe_tick"] < 5:
return {"launch": [], "steer": {}}
best, berr = "rad_math", float("inf")
if angs:
for k, (f, _) in HYP.items():
preds = [f(b) for b in PROBE]
err = sum(min(_adiff(a, p) for p in preds) for a in angs)
if err < berr:
best, berr = k, err
_S["mode"] = best
f, finv = HYP[best]
td = tick
remaining_slots = 36 - 4
batches = (remaining_slots + per_tick - 1) // max(1, per_tick)
last = td + batches - 1
T_arr = last + int(math.ceil((R - rho) / min(vs, vd))) + 4
_S["T_arr"] = T_arr
# finals of probe drones
finals = []
if angs:
for d in drones:
p = d.get("pos", [0, 0])
x, y = p[0] - ax, p[1] - ay
r = math.hypot(x, y)
phi = math.atan2(y, x)
L = vs * (T_arr - tick)
finals.append(phi + _theta(r, rho, L))
else:
for b in PROBE:
L = vs * (T_arr - _S["probe_tick"])
finals.append(f(b) + _theta(R, rho, L))
offset = finals[0] if finals else 0.0
step = 2 * math.pi / 36
slots = [offset + j * step for j in range(36)]
used = set()
for fa in finals:
bj, bd = None, 9.0
for j, s in enumerate(slots):
if j in used:
continue
dd = _adiff(s, fa)
if dd < bd:
bj, bd = j, dd
if bj is not None:
used.add(bj)
free = [slots[j] for j in range(36) if j not in used]
plan = []
nb = max(1, batches)
for i, F in enumerate(free):
typ = "decoy" if i % 4 == 2 else "striker"
bt = i % nb
tl = td + bt
T = T_arr - 3 if typ == "decoy" else T_arr
spd = vd if typ == "decoy" else vs
L = spd * (T - tl)
phi = F - _theta(R, rho, L)
plan.append((tl, typ, finv(phi)))
_S["plan"] = plan
# Launch scheduled drones (strikers first within a tick)
due = [p for p in _S["plan"] if p[0] <= tick]
due.sort(key=lambda p: (p[0], 0 if p[1] == "striker" else 1))
left = budget
sent = []
for p in due:
if len(launch) >= per_tick:
break
c = cs if p[1] == "striker" else cd
typ = p[1]
if left + 1e-9 < c:
if left + 1e-9 >= cd:
typ, c = "decoy", cd
else:
sent.append(p)
continue
launch.append({"type": typ, "bearing": float(p[2])})
left -= c
sent.append(p)
if sent:
ss = set(id(p) for p in sent)
_S["plan"] = [p for p in _S["plan"] if id(p) not in ss]
# Steering: phase-lock arrival at the id ring
T_arr = _S["T_arr"]
if T_arr is not None:
for d in drones:
try:
did = d.get("id")
p = d.get("pos", [0, 0])
x, y = p[0] - ax, p[1] - ay
r = math.hypot(x, y)
if r <= rho + 6.0:
continue
is_decoy = d.get("type") == "decoy"
T = T_arr - 3 if is_decoy else T_arr
spd = vd if is_decoy else vs
L = spd * (T - tick)
if L <= (r - rho) + 1.0:
steer[did] = None
continue
th = _theta(r, rho, L)
phi = math.atan2(y, x) + th
steer[did] = [ax + rho * math.cos(phi), ay + rho * math.sin(phi)]
except Exception:
continue
return {"launch": launch, "steer": steer}