Advent of code 2018/22
Ajax Direct

Answer

Part 1 :
Part 2 :
[$depth, $tx, $ty] = $input->numbers;

// ==================================================
// > PART 1 : BUILD THE CAVE
// ==================================================
$erosion    = [];
$cave       = [];
$solution_1 = 0;

for ($y = 0; $y <= $ty + 50; $y++) for ($x = 0; $x <= $tx + 50; $x++) {
    $erosion[$y][$x] = match (true) {
        ($x == 0) => ($y * 48271 + $depth) % 20183,
        ($y == 0) => ($x * 16807 + $depth) % 20183,
        ($x == $tx && $y == $ty) => $depth % 20183,
        default => (($erosion[$y][$x - 1] * $erosion[$y - 1][$x]) + $depth) % 20183
    };
    $cave[$y][$x] = $erosion[$y][$x] % 3;
    if ($x <= $tx && $y <= $ty) {
        $solution_1 += $cave[$y][$x];
    }
}

// ==================================================
// > PART 2 : EXPLORE
// ==================================================
$graph = new Graph(function ($graph) use ($cave) {
    [$x, $y, $object] = explode(";", $graph->current);

    $states = [];
    foreach (neighbors([$x, $y], false, 0, INF, 0, INF) as [$sx, $sy]) {
        if (!isset($cave[$sy][$sx])) continue;

        // Get object in common between current region and next one
        // 0 nothing, 1 climbing gear, 2 torch
        $picks = array_values(array_intersect(
            [[1, 2], [0, 1], [0, 2]][$cave[$y][$x]],
            [[1, 2], [0, 1], [0, 2]][$cave[$sy][$sx]]
        ));

        // If we can keep the object, always choose to do so
        if (in_array($object, $picks)) {
            $states["$sx;$sy;$object"] = 1;
        } else {
            $states["$sx;$sy;$picks[0]"] = 8;
        }
    }
    return $states;
});

$solution_2 = $graph->explore("0;0;2", "$tx;$ty;2")[1];