AI to avoid obstacle
You are probably aware of this but representing floating point numbers in JavaScript is difficult to do without rounding errors
Are you sure you the math is correct in the calculations? Specifically, when subtracting angleObstacle from moveAngle, should angleTarget really be added? I attempted to change it such that angleTarget does not get added when that happens. It appears to allow the green circle to avoid colliding with the obstacle circle. I am not sure why the speed changes... perhaps because of the exponential change with the arc tan or other sin functions.


const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');

const targetX = 50;
const targetY = 50;

const obstacleX = 150;
const obstacleY = 50;

let aiX = 250;
let aiY = 51;

function loop() {
    // Distance between the vector points
    const disTargetX = targetX - aiX;
    const disTargetY = targetY - aiY;
    
    const disObstacleX = obstacleX - aiX;
    const disObstacleY = obstacleY - aiY;

    // Moves to target by default
    const angleTarget = Math.atan2(disTargetY, disTargetX);
    let moveAngle = angleTarget;

    // If near obstacle, adjust course and try to avoid it
    if (Math.sqrt(disObstacleX * disObstacleX + disObstacleY * disObstacleY) &lt; 60) {
        const angleObstacle = Math.atan2(disObstacleY, disObstacleX);
        moveAngle += /*angleTarget -*/angleObstacle;
    }

    // Move the vector to desired location
    aiX += Math.cos(moveAngle);
    aiY += Math.sin(moveAngle);

    //Drawing
    ctx.clearRect(0, 0, 600, 200);

    ctx.beginPath();
    ctx.fillStyle = "teal";
    ctx.arc(aiX, aiY, 10, 0, Math.PI * 2, true);
    ctx.fill();

    ctx.beginPath();
    ctx.fillStyle = "purple";
    ctx.arc(obstacleX, obstacleY, 10, 0, Math.PI * 2, true);
    ctx.fill();
    
    ctx.rect(targetX - 20, targetY - 20,40,40);
    ctx.stroke();
    
    if (aiX &gt; 50) {
        requestAnimationFrame(loop);
    }
}

requestAnimationFrame(loop);
&lt;canvas id="canvas" width="600" height="200"&gt;&lt;/canvas&gt;



Other review points
In the code snippet above, you will notice that let has been replaced with const for many of the declarations of variables that are not re-assigned within the function - e.g. targetX, targetY, obstacleX, obstacleY, disTargetX, disTargetY . This avoids any unintentional re-assignment.
Also, the original code loops forever. I added a condition in the snippet above - i.e. aiX &gt; 50 which needs to be true in order for the function to be called again (via requestAnimationFrame). This avoids unnecessary processing.