pub fn advance_entity(
sink: &mut dyn FightSink,
config: &GameConfig,
lookups: &ContentLookups,
fight: &ActiveFight,
entity: &Entity,
ability: &Ability,
) -> Result<(), Error>Expand description
Native: move entity toward the enemy line in a single multi-cell run
(one StartMove instead of one per cell — every per-cell seam quantizes
to the 100ms game tick and gives the client a chance to stutter).
Movement is fixed-direction (allies always advance +x, enemies −x), so we
only ever advance toward the nearest opponent ahead — never chase one we
have already reached or passed, which would march us away from the fight
forever. Against a mobile opponent we cover half the gap and against a
static one the whole gap (see the max_steps comment): both produce one
StartMove, so an approach is a single smooth run, not one per cell.
Two mutually-approaching mobile runners meet in the middle without landing
on or crossing each other: at fight start both plan on the same tick, each
covers its half, and they end adjacent. A Run lowers to an immediate
StartMove and the event loop is depth-first LIFO, so the first planner’s
handle_start_move sets its move_target reservation BEFORE the second
plans this tick; the second’s probe routes every cell through
cell_exists_and_free (checks coordinates AND reserved move_target), so
when both aim at the same midpoint the second takes the cell beside it.
(The entrance “run onto the screen → run in the fight” seam is a separate,
client-side concern — Unity’s run-in tween must flow into the first
StartMove instead of force-settling to Idle; no server distance can cover
it.)
The walk also stops on the first cell where ability gains a valid target,
and never steps onto a cell occupied or reserved by another entity.
Neither team may cross the other’s front line. A mover advances at most to
the column directly in front of the opposing team’s frontmost LIVING unit and
never onto or past it, so the two sides meet exactly one column apart instead
of stacking on the same column (an enemy standing right under the hero). The
opposing front is taken over both current cells AND reserved move_targets,
so two mutual approachers don’t both land on the same midpoint column on an
even gap: the depth-first / LIFO second planner sees the first’s reservation
and stops one column short. This is the hard guarantee layered on top of the
half-gap / max_steps heuristic and the in-range stop.