Answer ⏱  572ms
                        
                                                        Part 1 : 
                            441
                            Part 2 : 
                            ⭐
                        
                     
                    $grid    = (new Grid($input));
[$w, $h] = [$grid->width(), $grid->height()];
$pos = (array) $grid->filter(fn ($c) => $c != ".")->reduce(function ($carry, $c, $xy) {
    $carry[(int) ($c == "v")][$xy[0]][$xy[1]] = true;
    return $carry;
}, set([]))->sortKeys();
$solution_1 = 0;
while (true) {
    $solution_1++;
    $moved = false;
    foreach ($pos as $d=>$p) {
        $npos = [];
        foreach ($p as $x=>$px) foreach ($px as $y=>$py) {
            [$nx, $ny] = [$x, $y];
            if ($d) // Down
                $ny = ($y+1)%$h;
            else // Left
                $nx = ($x+1)%$w;
            if (!isset($pos[0][$nx][$ny]) && !isset($pos[1][$nx][$ny])) {
                $npos[$d][$nx][$ny] = true;
                $moved = true;
            } else {
                $npos[$d][$x][$y] = true;
            }
        }
        $pos[$d] = $npos[$d];
    }
    if (!$moved) break;
}
$solution_2 = "⭐";