I've been looking through a lot of answers here:

- https://gamedev.stackexchange.com/questions/158825/resolving-circle-circle-collision
- https://gamedev.stackexchange.com/questions/26439/circle-to-circle-collision-checking-each-circle-against-all-others
- https://gamedev.stackexchange.com/questions/136520/nestling-into-contact-with-a-group-of-physics-objects-without-exerting-forces-on/136595#136595
- https://stackoverflow.com/questions/345838/ball-to-ball-collision-detection-and-handling
- https://stackoverflow.com/questions/53010947/detect-collision-between-two-circles-and-sliding-them-on-each-other
- https://gamedev.stackexchange.com/questions/199274/sliding-character-inside-the-circle
- https://gamedev.stackexchange.com/questions/147493/2d-circle-detection-algorithm-partially-working
- https://gamedev.stackexchange.com/questions/40547/box2d-too-much-for-circle-circle-collision-detection
- https://gamedev.stackexchange.com/questions/103278/circle-collision-resolution-and-angular-momentum
- https://flatredball.com/documentation/tutorials/math/circle-collision/
- https://stackoverflow.com/questions/61161303/how-do-i-simulate-ball-ball-collision-without-having-the-balls-bounce --doesnt show sliding
- https://stackoverflow.com/questions/26216173/accelerometer-rolling-ball-in-ball -- not sure how to implement in my case for angle `a`

I couldn't find exactly the same thing I was looking for, which is proper top-down character sliding of circle to circle movement.

I tried implementing things from other answers here but whenever the character moves around circle, for some reason it just goes inside it (I was able to get it to spin around edges at one point but was unpredictable)

<!-- begin snippet: js hide: false console: true babel: false -->

<!-- language: lang-js -->

    var aD = []
    var r

    function start() {
      r = new CanvasRenderer(can)
      my = new scene();
      eventHandler();

      my.add(new mesh({
        position: {
          x: 700,
          y: 200
        },
        radius: 70,
        color: "yellow",
        primitive: "circle",
        collision: "circle"
      }))

      my.add(new mesh({
        primitive: "circle",

        collision: "circle",
        color: "blue",
        name: "player",
        radius: 40,
        position: {
          x: 311,
          y: 75
        },
        origin: {
          x: 0.5,
          y: 0.5
        },
        onupdate(me) {


          aD.push(() => {
            r.ctx.beginPath()
            r.ctx.fillStyle = "red"
            r.ctx.arc(me.position.x, me.position.y, 5, 0, Math.PI * 2);
            r.ctx.fill()
            r.ctx.closePath();
          })

          if (!window.pl) pl = me
          var upKey = keys[38],
            downKey = keys[40],
            rightKey = keys[39],
            leftKey = keys[37],
            drx = 0,
            dx = 0,
            speed = 5,
            turningSpeed = 3
          //console.log(upKey)
          drx = leftKey ? -1 : rightKey ? 1 : 0
          forward = upKey ? 1 : downKey ? -1 : 0

          me.rotation.x += (
            (drx * Math.PI / 180 * turningSpeed)
          )
          me.rotation.y = 1;

          var xDir = Math.cos(me.rotation.x)
          var yDir = Math.sin(me.rotation.x)

          var xvl = xDir * forward * speed
          var yvl = yDir * forward * speed
          me.position.y //=col.y
          me.position.x // = col.x
          me.xvl = xvl
          me.yvl = yvl
          var next = {
            x: me.position.x + xvl,
            y: me.position.y + yvl
          }
          me.next = next
          var isColliding = []
          var coll;
          var curColls = []
          me.cols = []

          me.speed = speed
          me.yDir = yDir
          for (var i = 0; i < my.objects.length; i++) {
            let cur = my.objects[i];
            if (cur.name == me.name || cur == me) {
              break;
            }
            curColls = checkCollision(
              next.x,
              next.y,
              me.radius || me.scale.x / 2,
              cur
            )


            curColls.forEach(coll => {
              if (!coll) return;
              if (coll == me) {
                //	console.log("WH")
                return
              }


              cur.isCol = true
              isColliding.push({
                coll,
                object: cur
              })
              //	console.log(2222,coll.line)

            })
          }

          Array.from(me.cols)
            .forEach(q => {
              var w = isColliding.indexOf(q)
              if (w == -1 || q.object == me) {
                me.cols.splice(me.cols.indexOf(q), 1)
              }


            })
          var isme = me.cols.findIndex(q => q.object == me)
          if (isme > -1) {
            me.cols.splice(isme, 1)
            var ok = isColliding.findIndex(q => q.object == me)
            if (ok > -1) {
              isColliding.splice(ok, 1)
            }
          }
          if (!me.obs)
            me.obs = []
          var noad = false
          if (isColliding.length > 0) {

            isColliding.forEach(q => {
              var w = me.cols.indexOf(q)
              if (w == -1) {
                me.cols.push(q)
              }
            })





            var col = me.cols[0].coll
            var ob = me.cols[0].object
            if (!me.obs)
              me.obs = []

            if (col.type == "circle") {
              let vecBetweenX = next.x - ob.position.x;
              let vecBetweenY = next.y - ob.position.y;

              let heading = Math.atan2(vecBetweenY, vecBetweenX);

              var dst = Math.sqrt(
                vecBetweenX * vecBetweenX +
                vecBetweenY * vecBetweenY
              )
              var dirX = vecBetweenX / dst
              var dirY = vecBetweenY / dst

              var rt = heading

              var ris = ob.radius + me.radius

              var ax = Math.cos(rt) * ris
              var ay = Math.sin(rt) * ris

              /*
              var nx=ob.position.x+ax
              var ny=ob.position.y+ay
              */

              var nx = ob.position.x + dirX * ris
              var ny = ob.position.y + dirY + ris

              me.position.x = nx
              me.position.y = ny
              return


            }
          }

          me.position.x += xvl

          me.position.y += yvl
        }

      }));
      let i = setInterval(() => render(r, my), 16);
      r.on("resize", () => render(r, my));

    }


    function render(r, s) {

      s.update();
      r.render(s)
      aD.forEach(x => x());
      aD = []
    }

    onload = start;


    function checkCollision(x1, y1, rad, ob) {
      var colls = [];
      if (ob.collision == "circle") {
        /*
        	Ball 1:  center: p1=(x1,y1)  radius: r1
        	Ball 2:  center: p2=(x2,y2)  radius: r2

        	collision distance: R= r1 + r2
        	actual distance:    r12= sqrt( (x2-x1)^2 + (y2-y1)^2 )

        	collision vector: d12= (x2-x1,y2-y1) = (dx,dy)
        	actual distance:  r12= sqrt( dx*dx + dy*dy )
        */

        var r1 = rad;
        var r2 = ob.radius;

        var p1 = [x1, y1]
        var p2 = [ob.position.x, ob.position.y]

        var R = r1 + r2;
        var R2 = R * R;

        var dx = p2[0] - p1[0]
        var dy = p2[1] - p1[1]

        var r12 = (
          dx * dx + dy * dy
        )

        var r12s = Math.sqrt(r12)

        var isCollision = r12s < R

        var colPoint = {
          x: (x1 * r2 + p2[0] * r1) /
            (r1 + r2),
          y: (y1 * r2 + p2[1] * r1) /
            (r1 + r2),
          type: "circle"
        };

        aD.push(() => {
          r.ctx.beginPath()
          r.ctx.fillStyle = "salmon"
          r.ctx.arc(colPoint.x, colPoint.y, 5, 0, Math.PI * 2);
          r.ctx.fill()
          r.ctx.closePath();
        })

        if (isCollision) {
          colls.push(colPoint)
        }
      }

      return colls;


    }


    function dot(x1, y1, x2, y2) {
      return (
        x1 * x2 + y1 * y2
      )
    }


    function eventHandler() {
      window.keys = {};
      addEventListener("keyup", e => {
        keys[e.keyCode] = false;

      });

      addEventListener("keydown", e => {
        keys[e.keyCode] = true;
      });
    }

    function CanvasRenderer(dom) {
      if (!dom) dom = document.createElement("canvas");

      var events = {},
        self = this;

      function rsz() {
        dom.width = dom.clientWidth;
        dom.height = dom.clientHeight;
        self.dispatchEvent("resize");
      }

      window.addEventListener("resize", rsz);

      let ctx = dom.getContext("2d");

      function render(scene) {
        ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
        for (let i = 0; i < scene.objects.length; i++) {
          let o = scene.objects[i],
            verts = o.realVerts;


          if (o.primitive == "circle") {
            var fnc = o.drawPrimitive[o.primitive]
            if (typeof(fnc) == "function")
              fnc(ctx)
          }
          ctx.fillStyle = o.color || "blue";
          ctx.lineWidth = 1;
          ctx.fill()
          ctx.stroke();
        }
      }

      Object.defineProperties(this, {
        domElement: {
          get: () => dom
        },
        ctx: {
          get: () => ctx
        },
        render: {
          get: () => render
        },
        on: {
          get: () => (nm, cb) => {
            if (!events[nm]) {
              events[nm] = [];
            }
            events[nm].push(data => {
              if (typeof cb == "function") {
                cb(data);
              }
            });
          }
        },
        dispatchEvent: {
          get: () => (name, data) => {
            if (events[name]) {
              events[name].forEach(x => {
                x(data);
              });
            }
          }
        }
      });

      rsz();

    }

    function scene() {
      let objects = [];
      Object.defineProperties(this, {
        add: {
          get: () => obj => {
            objects.push(obj);
          }
        },
        objects: {
          get: () => objects
        },
        update: {
          get: () => () => {
            objects.forEach(x => {
              if (typeof x.update == "function") {
                x.update();
              }
            });

          }
        }
      });
    }

    function mesh(data = {}) {
      let self = this,
        holder = {
          position: {},
          scale: {

          },
          rotation: {},
          origin: {}
        },
        position = {},
        scale = {},
        rotation = {},
        collision = "mesh",
        shape = null,
        origin = {},
        color,
        radius = 0,
        name,
        primitive,

        eventNames = "update",
        events = {},
        drawPrimitive = {
          circle(ctx) {
            ctx.beginPath();
            ctx.arc(
              self.position.x,
              self.position.y,
              radius,
              0,
              360 * Math.PI / 180
            );
            ctx.closePath();
          }
        },
        width = 1,
        height = 1;

      Object.defineProperties(position, {
        x: {
          get: () => holder.position.x || 0,
          set: v => holder.position.x = v
        },
        y: {
          get: () => holder.position.y || 0,
          set: v => holder.position.y = v
        },
        z: {
          get: () => holder.position.z || 0,
          set: v => holder.position.z = v
        }
      });

      Object.defineProperties(scale, {
        x: {
          get: () => holder.scale.x || 1,
          set: v => holder.scale.x = v
        },
        y: {
          get: () => holder.scale.y || 1,
          set: v => holder.scale.y = v
        },
        z: {
          get: () => holder.scale.z || 1,
          set: v => holder.scale.z = v
        }
      });

      Object.defineProperties(rotation, {
        x: {
          get: () => holder.rotation.x || 0,
          set: v => holder.rotation.x = v
        },
        y: {
          get: () => holder.rotation.y || 0,
          set: v => holder.rotation.y = v
        },
        z: {
          get: () => holder.rotation.z || 0,
          set: v => holder.rotation.z = v
        }
      });


      Object.defineProperties(this, {

        name: {
          get: () => name,
          set(v) {
            name = v
          }
        },
        primitive: {
          get: () => primitive,
          set(v) {
            primitive = v;

          }
        },

        drawPrimitive: {
          get: () => drawPrimitive
        },
        width: {
          get: () => width,
          set(v) {
            width = v
          }
        },
        height: {
          get: () => height,
          set(v) {
            height = v
          }
        },
        position: {
          get: () => position,
          set: v => {
            position.x = v.x || 0;
            position.y = v.y || 0;
            position.z = v.z || 0;
          }
        },

        radius: {
          get: () => radius || (width * scale.x) / 2,
          set: v => {
            radius = v;
            this.width = radius / 2
            this.height = radius / 2
          }
        },
        scale: {
          get: () => scale,
          set: v => {
            scale.x = v.x || v.x === 0 ? v.x : 1;
            scale.y = v.y || v.y === 0 ? v.y : 1;
            scale.z = v.z || v.z === 0 ? v.z : 1;


          }
        },
        rotation: {
          get: () => rotation,
          set: v => {
            rotation.x = v.x || 0;
            rotation.y = v.y || 0;
            rotation.z = v.z || 0;
          }
        },
        color: {
          get: () => color,
          set: v => {
            color = v;
          }
        },
        update: {
          get: () => () => {
            if (events["update"]) {
              events.update.forEach(x => {

                x(this);
              });
            }
          }
        },
        on: {
          get: () => (nm, fnc) => {
            if (!events[nm]) events[nm] = [];
            events[nm].push(stuff => {
              if (typeof fnc == "function") {
                fnc(stuff);
              }
            });
          }
        }
      });



      eventNames.split(" ").forEach(x => {
        var name = "on" + x;
        if (!this.hasOwnProperty(name)) {
          Object.defineProperty(this, name, {
            get: () => events[name],
            set(v) {
              events[x] = [
                data => {
                  typeof v == "function" && v(data)
                }
              ];
            }
          });
        }
      });

      for (let k in data) {
        this[k] = data[k]
      }


    }

<!-- language: lang-css -->

    canvas {
      width: 100%;
      height: 100%;
      position: absolute;
      top: 0;
      left: 0px
    }

    .wow {
      float: right;
      z-index: 1298737198
    }

<!-- language: lang-html -->

    <br> game test 2d minimal<br>
    <meta charset="utf-8">
    <button onclick="start()" class=wow>ok</button>
    <canvas id=can></canvas>

<!-- end snippet -->

Just arrow keys to move

Relevant part for circle sliding (contains code from different examples that was used at different times but nothing works):

```
if(col.type=="circle"){
    let vecBetweenX = next.x - ob.position.x;
    let vecBetweenY = next.y - ob.position.y;

    let heading = Math.atan2(vecBetweenY, vecBetweenX);

    var dst = Math.sqrt(
        vecBetweenX*vecBetweenX
        +
        vecBetweenY*vecBetweenY
    )
    var dirX=vecBetweenX/dst
    var dirY=vecBetweenY/dst

    var rt= heading
    
    var ris = ob.radius + me.radius

    var ax = Math.cos(rt)*ris
    var ay = Math.sin(rt)*ris

    /*
    var nx=ob.position.x+ax
    var ny=ob.position.y+ay
    */
    
    var nx=ob.position.x+dirX*ris
    var ny=ob.position.y+dirY+ris
    
    me.position.x = nx
    me.position.y = ny
    return
}
```

I tried using method suggested in answer, but player is still getting stuck at circle, while the ideal intended behavior is for the player to slide along the edges