Write a Tactic
You command the four escorts by writing one JavaScript function. This page walks through Time to Impact, the tactic that holds the standoff raid, so you can read it, change a line and write your own. No prior JavaScript needed, the whole thing is one short function.
The idea
A tactic is a single function called plan. The simulator runs the engagement and calls your plan repeatedly, handing you the current picture each time. You return where each escort should go. That is the whole job: given what you can see right now, decide where the escorts should be.
You do not control firing. Each escort fires automatically at the nearest threat in range. Your only lever is positioning. A good tactic is one that puts the right escort in the right place at the right moment.
What you are given, what you return
Every tactic in the editor opens with this brief. It is the contract between your code and the simulator.
// BLUE C2 — command your interceptors.
// Implement: function plan(state) { ...; return orders }
// state.hvu { x, y, integrity }
// state.interceptors[] { id, x, y, ready } // ready=false while reloading
// state.threats[] { id, x, y }
// state.bounds { w, h }
// Return one waypoint per interceptor: [{ id, x, y }, ...]
// Interceptors auto-fire at the nearest threat in range.
// Optional: declare a PARAMS object to expose live sliders, then read them via plan(state, p).
So state is the picture: the ship (state.hvu), your escorts (state.interceptors, each with a ready flag that is false while it reloads), the inbound raid (state.threats) and the size of the arena (state.bounds). You return an array with one entry per escort, matched by id, each giving a point to steer toward.
Warm-up: the simplest tactic
Before Time to Impact, here is the shortest useful tactic. Send every escort at its nearest threat.
function plan(state){
const d = (a, b) => Math.hypot(a.x - b.x, a.y - b.y);
return state.interceptors.map(it => {
let best = null, bd = Infinity;
for (const t of state.threats) { const dd = d(it, t); if (dd < bd) { bd = dd; best = t; } }
return { id: it.id, x: best ? best.x : state.hvu.x, y: best ? best.y : state.hvu.y };
});
}
Read it from the inside out. d is a little distance helper. For each escort, the loop scans every threat and keeps the closest one. The escort is then ordered to that threat's position, or to the ship if the sky is clear. This is greedy targeting, and it teaches the shape of every tactic: map over the escorts, return one waypoint each. It also has a famous weakness. The escorts chase threats so far that they leave the ship open, which is why a leash on how far they may roam turns it from useless into effective.
Time to Impact, line by line
Here is the tactic that holds the standoff raid in full.
function plan(state){
const d = (a, b) => Math.hypot(a.x - b.x, a.y - b.y), hvu = state.hvu;
// most urgent threat = closest to the HVU
const ranked = [...state.threats].sort((a, b) => d(a, hvu) - d(b, hvu));
const free = [...state.interceptors], orders = [];
for (const t of ranked) {
if (!free.length) break;
let bi = 0, bd = Infinity;
free.forEach((it, i) => { const dd = d(it, t); if (dd < bd) { bd = dd; bi = i; } });
const it = free.splice(bi, 1)[0];
orders.push({ id: it.id, x: t.x, y: t.y });
}
// spare interceptors escort the HVU
free.forEach((it, i) => orders.push({ id: it.id, x: hvu.x + 40, y: hvu.y + (i ? 60 : -60) }));
return orders;
}
1 · Rank the threats by who strikes soonest
This is the whole idea. Deal with the threat that will reach the ship first. Because every threat moves at the same speed and heads straight for the ship, the one nearest the ship is the one that strikes soonest. So sorting the threats by distance to the HVU gives us their order of urgency. That ordering is the time to impact.
2 · Give each threat the nearest free escort
Now walk down the ranked list, most urgent first. For each threat, find the closest escort that has not already been assigned, order it to intercept, and take it out of the pool with splice so no escort is given two jobs. The most urgent threats get first pick of the closest escort. Less urgent threats take what is left.
3 · Park the spares
If there are more escorts than threats, the leftovers tuck in just ahead of the ship as a last line. Simple, and good enough for now.
Why it beats standoff
A fixed screen holds a ring and waits. Once the enemy can strike from outside that ring, the screen is finished. Time to Impact never holds a ring. It sends escorts out to meet the soonest-striking threats wherever they are, so they are killed before they reach their firing range. It defends against the threats, not a place. That is the difference between losing the convoy and getting it through.
Adding a dial
If you want a live slider, declare a PARAMS object and the editor draws it for you. Your plan then takes a second argument, p, holding the current values.
const PARAMS = {
radius: { label: 'Deployment range (units from HVU)', min: 20, max: 140, step: 2, default: 110 }
};
function plan(state, p){
const R = p.radius; // R is the live slider value
// ...use R to position the escorts...
}
That is how the surround screen exposes its deployment range. Anything you put in PARAMS becomes a control you can turn while the engagement runs.
Now make it better
Time to Impact is deliberately simple. Every shortcut in it is a door to a better tactic.
- Real time to impact. Ranking by distance assumes every threat has the same speed and heading. When that breaks, compute time to impact properly, as range divided by closing speed, and rank on that.
- Mind the reload. An escort with
readyfalse cannot fire. Sending it after an urgent threat it cannot engage wastes it. Prefer ready escorts for the threats that matter. - Cover the bearings. Time to Impact can send two escorts after threats from the same side and leave another open. When the raid splits across bearings, and it will, coverage starts to beat raw proximity.
- Use the spares. The leftovers just sit by the ship. Could they hold a forward screen on the threat axis instead.
Those are exactly the questions that get hard in the next scenario. Which is the point.
Choose Day 3, switch to Source, and you will find this exact code waiting. Change it, run it, see what happens.