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

PhaseWhat Happens
GuardCheck if autopilot is enabled, has a command, and command hasn’t expired
CadenceNormal = 200ms between decisions; Danger = 80ms (react faster)
SensesBuild snapshot of pellets + NPCs in polar coordinates relative to player head
IntentMap eat→food, hunt→snake, avoid→opposite of snake, else→survive
CandidatesNormal = 3 options (±30°, 0°); Danger = 7 options (±90°, ±60°, ±30°, 0°)
ScoringfoodScore + runwayBonus - avoidPenalty - turnPenalty
SelectionPick highest-scoring non-dangerous option; fallback to max clearance in emergency
ApplyMutate state.angle by chosen turn amount

Constants Reference

ConstantValuePurpose
AUTOPILOT_DECISION_MS200Normal decision interval
AUTOPILOT_DANGER_DECISION_MS80Faster decisions when danger detected
SELF_CLEARANCE_LOOKAHEAD_PX1200Max ray-cast distance
DANGER_PX~160-360Heavy penalty zone threshold
LOOKAHEAD_PX~520+Light penalty zone threshold
TURN_DEG30Base turn increment

Intent Behaviors

IntentTargetBehavior
eatNearest pelletChase food, avoid obstacles
hunt / interceptNearest snake headChase snake head aggressively
avoid / escape180° from nearest snakeFlee from nearest snake
survive / unknownNoneMaximize clearance, go straight