Worm Commander Autopilot - Sequence Diagram
sequenceDiagram
participant GL as Game Loop (frame)
participant AP as applyAutopilot()
participant SE as getWormSenses()
participant RC as rayClearanceToSelf()
participant RN as rayClearanceToSnakes()
participant ST as State
Note over GL: Every ~16ms (60fps)
GL->>AP: applyAutopilot(dt)
%% Guard checks
AP->>ST: Check state.autopilot
alt No autopilot command
AP-->>GL: return (no-op)
end
AP->>ST: Check autopilotEnabled
alt Autopilot disabled
AP-->>GL: return (no-op)
end
AP->>ST: Check cmd.until_ms
alt Command expired
AP->>ST: state.autopilot = null
AP-->>GL: return
end
%% Decision cadence
AP->>RC: rayClearanceToSelf(0, 0)
RC-->>AP: straightSelfClear (px)
alt straightSelfClear < 420
Note over AP: DANGER MODE<br/>decisionEveryMs = 80ms
else Normal
Note over AP: NORMAL MODE<br/>decisionEveryMs = 200ms
end
AP->>ST: Check time since last decision
alt Too soon
AP-->>GL: return (skip this frame)
end
%% Gather senses
rect rgb(240, 248, 255)
Note over SE: Build world snapshot
AP->>SE: getWormSenses()
SE->>ST: Read state.trail (player body)
SE->>ST: Read state.foods (pellets)
SE->>ST: Read state.npcs (other snakes)
loop For each pellet in range
SE->>SE: Calculate dist, bearing_deg
end
loop For each NPC in range
SE->>SE: Calculate dist, bearing_deg, heading_deg
SE->>SE: Sample body_points_polar
SE->>SE: Calculate threat.min_body_dist
end
SE-->>AP: senses {you, pellets[], snakes[]}
end
%% Determine intent & target
rect rgb(255, 250, 240)
Note over AP: Determine target bearing
AP->>AP: Parse intent from cmd.intent
alt intent == "eat"
AP->>AP: selectFoodTarget(senses)
Note over AP: Pick nearest pellet<br/>(slight forward preference)
AP->>AP: targetBearingDeg = pellet.bearing_deg
else intent == "hunt" or "intercept"
AP->>AP: targetBearingDeg = senses.snakes[0].bearing_deg
Note over AP: Chase nearest snake head
else intent == "avoid" or "escape"
AP->>AP: targetBearingDeg = snake.bearing_deg + 180°
Note over AP: Flee from nearest snake
else Unknown intent (survive)
AP->>AP: targetBearingDeg = null
Note over AP: No pursuit target
end
end
%% Build candidate turns
rect rgb(245, 255, 245)
Note over AP: Score candidate turns
alt Danger mode (straightSelfClear < 360)
Note over AP: options = [-90°, -60°, -30°, 0°, +30°, +60°, +90°]
else Normal mode
Note over AP: options = [-30°, 0°, +30°]
end
loop For each turnDeg in options
%% Food/pursuit score
alt Has targetBearingDeg & not danger mode
AP->>AP: newBearing = targetBearing - turnDeg
AP->>AP: alignmentGain = |old| - |new|
AP->>AP: foodScore = alignmentGain × 8
else Survive mode
AP->>AP: foodScore = (turnDeg == 0) ? 50 : 0
end
%% Clearance checks
AP->>RC: rayClearanceToSelf(turnDeg)
RC->>ST: Scan state.trail for collision
RC-->>AP: selfClear (px)
AP->>RN: rayClearanceToSnakes(turnDeg)
RN->>ST: Scan state.npcs for collision
RN-->>AP: npcClear (px)
AP->>AP: minClear = min(selfClear, npcClear)
%% Scoring
AP->>AP: score = foodScore
AP->>AP: score += minClear × 0.08 (runway bonus)
alt minClear < DANGER_PX
AP->>AP: score -= (DANGER_PX - minClear) × 15
Note over AP: HEAVY penalty
else minClear < LOOKAHEAD_PX
AP->>AP: score -= (LOOKAHEAD_PX - minClear) × 0.5
Note over AP: Light penalty
end
AP->>AP: score -= |turnDeg| × 0.6 (turn penalty)
AP->>AP: Mark danger = (minClear < DANGER_PX)
end
end
%% Select best turn
rect rgb(255, 245, 245)
Note over AP: Select final turn
AP->>AP: Sort candidates by score (descending)
AP->>AP: safeOpt = first non-danger option
alt Danger mode & no safe options
AP->>AP: Choose max clearance option
Note over AP: Emergency: prioritize survival
else Has safe option
AP->>AP: chosen = safeOpt
else All dangerous
AP->>AP: chosen = highest score
Note over AP: CHOSE DANGEROUS PATH (logged)
end
end
%% Apply turn
AP->>ST: state.angle += chosen.turnDeg (radians)
AP->>AP: Log decision to console
AP-->>GL: return
%% Game continues
GL->>GL: applyHeldTurning(dt)
GL->>GL: step(dt) - move snake forward
GL->>GL: render()
Key Flows Summarized
| Phase | What Happens |
|---|
| Guard | Check if autopilot is enabled, has a command, and command hasn’t expired |
| Cadence | Normal = 200ms between decisions; Danger = 80ms (react faster) |
| Senses | Build snapshot of pellets + NPCs in polar coordinates relative to player head |
| Intent | Map eat→food, hunt→snake, avoid→opposite of snake, else→survive |
| Candidates | Normal = 3 options (±30°, 0°); Danger = 7 options (±90°, ±60°, ±30°, 0°) |
| Scoring | foodScore + runwayBonus - avoidPenalty - turnPenalty |
| Selection | Pick highest-scoring non-dangerous option; fallback to max clearance in emergency |
| Apply | Mutate state.angle by chosen turn amount |
Constants Reference
| Constant | Value | Purpose |
|---|
AUTOPILOT_DECISION_MS | 200 | Normal decision interval |
AUTOPILOT_DANGER_DECISION_MS | 80 | Faster decisions when danger detected |
SELF_CLEARANCE_LOOKAHEAD_PX | 1200 | Max ray-cast distance |
DANGER_PX | ~160-360 | Heavy penalty zone threshold |
LOOKAHEAD_PX | ~520+ | Light penalty zone threshold |
TURN_DEG | 30 | Base turn increment |
Intent Behaviors
| Intent | Target | Behavior |
|---|
eat | Nearest pellet | Chase food, avoid obstacles |
hunt / intercept | Nearest snake head | Chase snake head aggressively |
avoid / escape | 180° from nearest snake | Flee from nearest snake |
survive / unknown | None | Maximize clearance, go straight |