Advent of code 2021/25
Ajax Direct

Answer 966ms

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 = "⭐";