text:
Bridge the Gaps
Given a black and white image with a white background and a set of black dots, paint a set of white pixels red, such that there is a path between each pair of black pixels.
Details

A path is a set of connected pixels (8-neighbourhood connectivity). Black pixels can be used as part of the paths. The goal is trying to minimize the set of red pixels under the above conditions, and outputting a corresponding image.
You don't have to find the optimal solution.
A trivial and at the same time worst solution is just painting all the white pixels red.
Example (Pixels are enlarged for visibility):

Details

Given a pixel image (in any suitable format) return another image with the dots connected as specified above, as well as a integer indicating how many red pixel were used.
The Score is the product of (1+the number of red pixels) for each of the 14 testcases.
The goal is having the lowest score.

Testcases
The 14 testcases are shown below. A python program to verify the connectedness of the outputs can be found here.

Meta
Thanks to @Veskah, @Fatalize, @wizzwizz4 and @trichoplax for the various suggestions.

Good challenge; I like ones with different and creative scoring schemes. I assume the program needs to work on an arbitrary image, not just these 14 specific examples? If so, can we assume a reasonable max size, like 512x512 per the Mona Lisa image, or 1024x1024?

Thanks for the feedback! Yes you can assume a maximal size (an also minimal size if necessary), as long as all the 14 examples can be processed.

how do i convert png to ascii or json or something else easy to parse?

Do you have to be able to compute your own score? A program which tries every possible combination of white pixels to paint red, and sees which subset has the fewest red pixels while connecting all the black pixels would have the best possible score, but it would be so slow that it would take longer than the lifetime of the universe to actually compute that score.

@LeoTenenbaum Yes you do have to compute your own score.

@ngn I'd be surprized to find image processing libraries that cannot load .png files. If you use Python you could use for instance PIL/pillow.

@flawr thanks. this worked. i would use python only for i/o, and a faster golfy language for the rest.

@ngn Open in GIMP, save as netpbm format.

Python, 2.62 * 10^40
This algorithm just floodfills (BFS) the plane starting from the black parts of the image, where for each new pixel we record what black part it was flooded from. As soon as we have two neighbouring pixels with different black parts as ancestors, we basically merge these two black parts by joining them through the ancestors of the two neighbours we just found. In theory this could be implemented in O(#pixels), but to keep the amount of code at an acceptable level this implementation is slightly worse.

Output

import numpy as np
from scipy import ndimage
import imageio
from collections import deque

# path to your image
for k in range(1, 15):
    fname=str(k).zfill(2) +'.png'
    print("processing ", fname)

    # load image
    img = imageio.imread("./images/"+fname, pilmode="RGB")
    print(img.shape)

    # determine non_white part
    white = np.logical_and(np.logical_and(img[:,:,0] == 255, img[:,:,1] == 255), img[:,:,2] == 255)
    non_white = np.logical_not(white)

    # find connected components of non-white part
    neighbourhood = np.ones((3,3))
    labeled, nr_objects = ndimage.label(non_white, neighbourhood)

    # print result
    print("number of separate objects is {}".format(nr_objects))

    # start flood filling algorithm
    ind = np.nonzero(labeled)
    front = deque(zip(ind[0],ind[1]))

    membership = np.copy(labeled)
    is_merge_point = np.zeros_like(labeled) > 0
    parent = np.zeros((2,) + labeled.shape) #find ancestor of each pixel
    is_seed = labeled > 0
    size_i, size_j = labeled.shape
    # flood from every seed
    while front: #while we have unexplored pixels
        point = front.popleft()
        # check neighbours:
        for (di,dj) in [(-1,-1),(-1,0),(-1,1),(0,-1),(0,1),(1,-1),(1,0),(1,1)]:
            current = membership[point[0], point[1]]
            new_i, new_j = point[0]+di, point[1]+dj
            if 0 <= new_i < size_i and 0 <= new_j < size_j:
                value = membership[new_i, new_j]
                if value == 0:
                    membership[new_i, new_j] = current
                    front.append((new_i, new_j))
                    parent[:, new_i, new_j] = point
                elif value != current: #MERGE!
                    is_merge_point[point[0], point[1]] = True
                    is_merge_point[new_i, new_j] = True
                    membership[np.logical_or(membership == value, membership == current)] = min(value, current)

    # trace back from every merger
    ind = np.nonzero(is_merge_point)
    merge_points = deque(zip(ind[0].astype(np.int),ind[1].astype(np.int)))
    for point in merge_points:
        next_p = point
        while not is_seed[next_p[0], next_p[1]]:
            is_merge_point[next_p[0], next_p[1]] = True
            next_p = parent[:, next_p[0], next_p[1]].astype(np.int)

    # add red points:
    img_backup = np.copy(img)
    img[:,:,0][is_merge_point] = 255 * img_backup[:,:,0]
    img[:,:,1][is_merge_point] = 0   * img_backup[:,:,1]
    img[:,:,2][is_merge_point] = 0   * img_backup[:,:,2]

    #compute number of new points
    n_red_points = (img[:,:,0] != img[:,:,1]).sum()
    print("#red points:", n_red_points)

    # plot: each component should have separate color
    imageio.imwrite("./out_images/"+fname, np.array(img))

Score
(1+183)*(1+142)*(1+244)*(1+42)*(1+1382)*(1+2)*(1+104)*(1+7936)*(1+26)*(1+38562)*(1+42956)*(1+6939)*(1+8882)*(1+9916)
= 26208700066468930789809050445560539404000
= 2.62 * 10^40

--This, I believe, is optimal. Well done.-- Okay, this isn't optimal. I don't understand why not.

@wizzwizz4 Look at the easy case of the four corners of a square: The optimal solution would be an X. While in theory my algorithm could find this solution, it is very unlikely. It is a lot more likely that it finds a solution with three paths each connecting two points.

@wizzwizz4 Yep, zoom in on the wikipedia text example, and you'll see tons of little places where a different connecting path would have saved a red pixel or two; they'll add up.

But this seems like soap-bubbles on pegs, which is a legitimate solution to the Steiner tree problem.

@wizzwizz4 We're not measuring the added lines via "true" length, we're counting the number of (square) pixels. It takes the same number of red pixels to get from (0,0) to (10,10) as it does to get (from either) to (0,10). So it's not actually the Steiner tree problem.

@BradC It's not the Euclidean Steiner tree problem. dist(x, y) = max(x, y). The problem works for dist(x, y) = x + y, so it should still be fine.

It is indeed a Steiner tree problem, I actually wrote this challenge as a "simplification" of the continuous problem (see here), but - as you discussed - the metric does not seem to be induced by a metric on the continuous plane.

@wizzwizz4 The difference, then, must be that we're not connecting points, we're connecting sets of points, so we must not be deciding which points in each set to connect in an optimal way. Zoom into the text example again, the improvements you can see mostly have to do with which parts of each shape are connected.

C, score 2.397x10^38
Man this took way too long to do, most likely due to my choice of language. I got the algorithm working fairly early, but ran into a lot of problems with memory allocation (couldn't recursively free stuff due to stack overflows, leak sizes were huge).
Still! It beats the other entry on every test case, and might even be optimal gets pretty close or exactly optimal solutions a lot of the time.
Anyway, here's the code:
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#include <string.h>

#define WHITE 'W'
#define BLACK 'B'
#define RED   'R'

typedef struct image {
    int w, h;
    char* buf;
} image;

typedef struct point {
    int x, y;
    struct point *next;
    struct point *parent;
} point;

typedef struct shape {
    point* first_point;
    point* last_point;

    struct shape* next_shape;
} shape;

typedef struct storage {
    point* points;
    size_t points_size;
    size_t points_index;

    shape* shapes;
    size_t shapes_size;
    size_t shapes_index;
} storage;

char getpx(image* img, int x, int y) {
    if (0>x || x>=img->w || 0>y || y>=img->h) {
        return WHITE;
    } else {
        return img->buf[y*img->w+x];
    }
}

storage* create_storage(int w, int h) {
    storage* ps = (storage*)malloc(sizeof(storage));

    ps->points_size = 8*w*h;
    ps->points = (point*)calloc(ps->points_size, sizeof(point));
    ps->points_index = 0;

    ps->shapes_size = 2*w*h;
    ps->shapes = (shape*)calloc(ps->shapes_size, sizeof(shape));
    ps->shapes_index = 0;

    return ps;
}

void free_storage(storage* ps) {
    if (ps != NULL) {
        if (ps->points != NULL) {
            free(ps->points);
            ps->points = NULL;
        }
        if (ps->shapes != NULL) {
            free(ps->shapes);
            ps->shapes = NULL;
        }
        free(ps);
    }
}

point* alloc_point(storage* ps) {
    if (ps->points_index == ps->points_size) {
        printf("WHOAH THERE BUDDY SLOW DOWN\n");
        /*// double the size of the buffer
        point* new_buffer = (point*)malloc(ps->points_size*2*sizeof(point));
        // need to change all existing pointers to point to new buffer
        long long int pointer_offset = (long long int)new_buffer - (long long int)ps->points;
        for (size_t i=0; i<ps->points_index; i++) {
            new_buffer[i] = ps->points[i];
            if (new_buffer[i].next != NULL) {
                new_buffer[i].next += pointer_offset;
            }
            if (new_buffer[i].parent != NULL) {
                new_buffer[i].parent += pointer_offset;
            }
        }

        for(size_t i=0; i<ps->shapes_index; i++) {
            if (ps->shapes[i].first_point != NULL) {
                ps->shapes[i].first_point += pointer_offset;
            }
            if (ps->shapes[i].last_point != NULL) {
                ps->shapes[i].last_point += pointer_offset;
            }
        }

        free(ps->points);
        ps->points = new_buffer;
        ps->points_size = ps->points_size * 2;*/
    }
    point* out = &(ps->points[ps->points_index]);
    ps->points_index += 1;
    return out;
}

shape* alloc_shape(storage* ps) {
    /*if (ps->shapes_index == ps->shapes_size) {
        // double the size of the buffer
        shape* new_buffer = (shape*)malloc(ps->shapes_size*2*sizeof(shape));
        long long int pointer_offset = (long long int)new_buffer - (long long int)ps->shapes;
        for (size_t i=0; i<ps->shapes_index; i++) {
            new_buffer[i] = ps->shapes[i];
            if (new_buffer[i].next_shape != NULL) {
                new_buffer[i].next_shape += pointer_offset;
            }
        }
        free(ps->shapes);
        ps->shapes = new_buffer;
        ps->shapes_size = ps->shapes_size * 2;
    }*/
    shape* out = &(ps->shapes[ps->shapes_index]);
    ps->shapes_index += 1;
    return out;
}

shape floodfill_shape(image* img, storage* ps, int x, int y, char* buf) {
    // not using point allocator for exploration stack b/c that will overflow it

    point* stack = (point*)malloc(sizeof(point));
    stack->x = x;
    stack->y = y;
    stack->next = NULL;
    stack->parent = NULL;

    point* explored = NULL;
    point* first_explored;
    point* next_explored;

    while (stack != NULL) {
        int sx = stack->x;
        int sy = stack->y;
        point* prev_head = stack;
        stack = stack->next;
        free(prev_head);

        buf[sx+sy*img->w] = 1; // mark as explored

        // add point to shape
        next_explored = alloc_point(ps);
        next_explored->x = sx;
        next_explored->y = sy;
        next_explored->next = NULL;
        next_explored->parent = NULL;

        if (explored != NULL) {
            explored->next = next_explored;
        } else {
            first_explored = next_explored;
        }
        explored = next_explored;

        for (int dy=-1; dy<2; dy++) {
        for (int dx=-1; dx<2; dx++) {
            if (dy != 0 || dx != 0) {
                int nx = sx+dx;
                int ny = sy+dy;
                if (getpx(img, nx, ny) == WHITE || buf[nx+ny*img->w]) {
                    // skip adding point to fringe
                } else {
                    // push point to top of stack
                    point* new_point = (point*)malloc(sizeof(point));
                    new_point->x = nx;
                    new_point->y = ny;
                    new_point->next = stack;
                    new_point->parent = NULL;

                    stack = new_point;
                }
            }
        }
        }
    }

    /*if (getpx(img, x, y) == WHITE || buf[x+y*img->w]) {
        return (shape){NULL, NULL, NULL};
    } else {
        buf[x+y*img->w] = 1;

        shape e  = floodfill_shape(img, ps, x+1, y,   buf);
        shape ne = floodfill_shape(img, ps, x+1, y+1, buf);
        shape n  = floodfill_shape(img, ps, x,   y+1, buf);
        shape nw = floodfill_shape(img, ps, x-1, y+1, buf);
        shape w  = floodfill_shape(img, ps, x-1, y,   buf);
        shape sw = floodfill_shape(img, ps, x-1, y-1, buf);
        shape s  = floodfill_shape(img, ps, x,   y-1, buf);
        shape se = floodfill_shape(img, ps, x+1, y-1, buf);

        point *p = alloc_point(ps);
        p->x = x;
        p->y = y;
        p->next = NULL;
        p->parent = NULL;

        shape o = (shape){p, p, NULL};
        if (e.first_point != NULL) {
            o.last_point->next = e.first_point;
            o.last_point = e.last_point;
        }
        if (ne.first_point != NULL) {
            o.last_point->next = ne.first_point;
            o.last_point = ne.last_point;
        }
        if (n.first_point != NULL) {
            o.last_point->next = n.first_point;
            o.last_point = n.last_point;
        }
        if (nw.first_point != NULL) {
            o.last_point->next = nw.first_point;
            o.last_point = nw.last_point;
        }
        if (w.first_point != NULL) {
            o.last_point->next = w.first_point;
            o.last_point = w.last_point;
        }
        if (sw.first_point != NULL) {
            o.last_point->next = sw.first_point;
            o.last_point = sw.last_point;
        }
        if (s.first_point != NULL) {
            o.last_point->next = s.first_point;
            o.last_point = s.last_point;
        }
        if (se.first_point != NULL) {
            o.last_point->next = se.first_point;
            o.last_point = se.last_point;
        }

        return o;
    }*/

    shape out = {first_explored, explored, NULL};

    return out;
}

shape* create_shapes(image* img, storage* ps) {
    char* added_buffer = (char*)calloc(img->w*img->h, sizeof(char));
    shape* first_shape = NULL;
    shape* last_shape = NULL;
    int num_shapes = 0;
    for (int y=0; y<img->h; y++) {
        for (int x=0; x<img->w; x++) {
            if (getpx(img, x, y) != WHITE && !(added_buffer[x+y*img->w])) {
                shape* alloced_shape = alloc_shape(ps);
                *alloced_shape = floodfill_shape(img, ps, x, y, added_buffer);

                if (first_shape == NULL) {
                    first_shape = alloced_shape;
                    last_shape = alloced_shape;
                } else if (last_shape != NULL) {
                    last_shape->next_shape = alloced_shape;
                    last_shape = alloced_shape;
                }

                num_shapes++;
            }
        }
    }

    free(added_buffer);

    return first_shape;
}

void populate_buf(image* img, shape* s, char* buf) {
    point* p = s->first_point;

    while (p != NULL) {
        buf[p->x+p->y*img->w] = 1;
        p = p->next;
    }
}

bool expand_frontier(image* img, storage* ps, shape* prev_frontier, shape* next_frontier, char* buf) {
    point* p = prev_frontier->first_point;
    point* n = NULL;

    bool found = false;

    size_t starting_points_index = ps->points_index;

    while (p != NULL) {
        for (int dy=-1; dy<2; dy++) {
        for (int dx=-1; dx<2; dx++) {
            if (dy != 0 || dx != 0) {
                int nx = p->x+dx;
                int ny = p->y+dy;
                if ((0<=nx && nx<img->w && 0<=ny && ny<img->h) // in bounds
                        && !buf[nx+ny*img->w]) {               // not searched yet
                    buf[nx+ny*img->w] = 1;
                    if (getpx(img, nx, ny) != WHITE) {
                        // found a new shape!
                        ps->points_index = starting_points_index;
                        n = alloc_point(ps);
                        n->x = nx;
                        n->y = ny;
                        n->next = NULL;
                        n->parent = p;
                        found = true;
                        goto __expand_frontier_fullbreak;
                    } else {
                        // need to search more
                        point* f = alloc_point(ps);
                        f->x = nx;
                        f->y = ny;
                        f->next = n;
                        f->parent = p;
                        n = f;
                    }
                }
            }
        }}

        p = p->next;
    }
__expand_frontier_fullbreak:
    p = NULL;
    point* last_n = n;
    while (last_n->next != NULL) {
        last_n = last_n->next;
    }

    next_frontier->first_point = n;
    next_frontier->last_point = last_n;

    return found;
}

void color_from_frontier(image* img, point* frontier_point) {
    point* p = frontier_point->parent;

    while (p->parent != NULL) { // if everything else is right,
                                // a frontier point should come in a chain of at least 3
                                // (f point (B) -> point to color (W) -> point in shape (B) -> NULL)
        img->buf[p->x+p->y*img->w] = RED;
        p = p->parent;
    }
}

int main(int argc, char** argv) {
    if (argc < 3) {
        printf("Error: first argument must be filename to load, second argument filename to save to.\n");
        return 1;
    }

    char* fname = argv[1];
    FILE* fp = fopen(fname, "r");

    if (fp == NULL) {
        printf("Error opening file \"%s\"\n", fname);
        return 1;
    }

    int w, h;
    w = 0;
    h = 0;
    fscanf(fp, "%d %d\n", &w, &h);

    if (w==0 || h==0) {
        printf("Error: invalid width/height specified\n");
        return 1;
    }

    char* buf = (char*)malloc(sizeof(char)*w*h+1);
    fgets(buf, w*h+1, fp);
    fclose(fp);

    image img = (image){w, h, buf};

    int nshapes = 0;
    storage* ps = create_storage(w, h);

    while (nshapes != 1) {
        // main loop, do processing step until one shape left
        ps->points_index = 0;
        ps->shapes_index = 0;

        shape* head = create_shapes(&img, ps);
        nshapes = 0;
        shape* pt = head;
        while (pt != NULL) {
            pt = pt->next_shape;
            nshapes++;
        }
        if (nshapes % 1024 == 0) {
            printf("shapes left: %d\n", nshapes);
        }
        if (nshapes == 1) {
            goto __main_task_complete;
        }

        shape* frontier = alloc_shape(ps);
        // making a copy so we can safely free later
        point* p = head->first_point;
        point* ffp = NULL;
        point* flp = NULL;
        while (p != NULL) {
            if (ffp == NULL) {
                ffp = alloc_point(ps);
                ffp->x = p->x;
                ffp->y = p->y;
                ffp->next = NULL;
                ffp->parent = NULL;
                flp = ffp;
            } else {
                point* fnp = alloc_point(ps);
                fnp->x = p->x;
                fnp->y = p->y;
                fnp->next = NULL;
                fnp->parent = NULL;

                flp->next = fnp;
                flp = fnp;
            }

            p = p->next;
        }
        frontier->first_point = ffp;
        frontier->last_point = flp;
        frontier->next_shape = NULL;

        char* visited_buf = (char*)calloc(img.w*img.h+1, sizeof(char));
        populate_buf(&img, frontier, visited_buf);

        shape* new_frontier = alloc_shape(ps);
        new_frontier->first_point = NULL;
        new_frontier->last_point = NULL;
        new_frontier->next_shape = NULL;

        while (!expand_frontier(&img, ps, frontier, new_frontier, visited_buf)) {
            frontier->first_point = new_frontier->first_point;
            frontier->last_point = new_frontier->last_point;
            new_frontier->next_shape = frontier;
        }

        free(visited_buf);
        color_from_frontier(&img, new_frontier->first_point);
__main_task_complete:
        img = img;
    }

    free_storage(ps);

    char* outfname = argv[2];
    fp = fopen(outfname, "w");

    if (fp == NULL) {
        printf("Error opening file \"%s\"\n", outfname);
        return 1;
    }

    fprintf(fp, "%d %d\n", img.w, img.h);
    fprintf(fp, "%s", img.buf);

    free(img.buf);

    fclose(fp);

    return 0;
}

Tested on: Arch Linux, GCC 9.1.0, -O3
This code takes input/output in a custom file I call "cppm" (because it's like a condensed version of the classic PPM format). A python script to convert to/from it is below:
from PIL import Image

BLACK='B'
WHITE='W'
RED  ='R'

def image_to_cppm(infname, outfname):
    outfile = open(outfname, 'w')
    im = Image.open(infname)

    w, h = im.width, im.height
    outfile.write(f"{w} {h}\n")
    for y in range(h):
        for x in range(w):
            r, g, b, *_ = im.getpixel((x, y))
            if r==0 and g==0 and b==0:
                outfile.write(BLACK)
            elif g==0 and b==0:
                outfile.write(RED)
            else:
                outfile.write(WHITE)
    outfile.write("\n")
    outfile.close()
    im.close()

def cppm_to_image(infname, outfname):
    infile = open(infname, 'r')

    w, h = infile.readline().split(" ")
    w, h = int(w), int(h)

    im = Image.new('RGB', (w, h), color=(255, 255, 255))

    for y in range(h):
        for x in range(w):
            c = infile.read(1)
            if c==BLACK:
                im.putpixel((x,y), (0, 0, 0))
            elif c==RED:
                im.putpixel((x,y), (255, 0, 0))

    infile.close()
    im.save(outfname)
    im.close()

if __name__ == "__main__":
    import sys
    if len(sys.argv) < 3:
        print("Error: must provide 2 files to convert, first is from, second is to")

    infname = sys.argv[1]
    outfname = sys.argv[2]

    if not infname.endswith("cppm") and outfname.endswith("cppm"):
        image_to_cppm(infname, outfname)
    elif infname.endswith("cppm") and not outfname.endswith("cppm"):
        cppm_to_image(infname, outfname)
    else:
        print("didn't do anything, exactly one file must end with .cppm")

Algorithm explanation
How this algorithm works is that it starts by finding all the connected shapes in the image, including red pixels. It then takes the first one and expands its frontier one pixel at a time until it encounters another shape. It then colors all the pixels from the touching to the original shape (using the linkedlist it made along the way to keep track). Finally, it repeats the process, finding all the new shapes created, until there is only one shape left.
Image gallery

Testcase 1, 183 pixels

Testcase 2, 140 pixels

Testcase 3, 244 pixels

Testcase 4, 42 pixels

Testcase 5, 622 pixels

Testcase 6, 1 pixel

Testcase 7, 104 pixels

Testcase 8, 2286 pixels

Testcase 9, 22 pixels

Testcase 10, 31581 pixels

Testcase 11, 21421 pixels

Testcase 12, 5465 pixels

Testcase 13, 4679 pixels

Testcase 14, 7362 pixels

Nice work! Seems very efficient, although I can imagine a few shapes with slightly more optimal solutions: Testcase 3 (4 dots in a square), for example, I've (manually) gotten as low as 175 (a red X), not sure how I'd force that via algorithm.

Python 3: 1.7x10^42 1.5x10^41
Using Pillow, numpy and scipy.
Images are assumed to be in an images folder located in the same directory as the script.
Disclaimer: It takes a long time to process all the images.
Code
import sys
import os

from PIL import Image
import numpy as np
import scipy.ndimage

def obtain_groups(image, threshold, structuring_el):
    """
    Obtain isles of unconnected pixels via a threshold on the R channel
    """
    image_logical = (image[:, :, 1] < threshold).astype(np.int)
    return scipy.ndimage.measurements.label(image_logical, structure=structuring_el)

def swap_colors(image, original_color, new_color):
    """
    Swap all the pixels of a specific color by another color
    """
    r1, g1, b1 = original_color  # RGB value to be replaced
    r2, g2, b2 = new_color  # New RGB value
    red, green, blue = image[:, :, 0], image[:, :, 1], image[:, :, 2]
    mask = (red == r1) & (green == g1) & (blue == b1)
    image[:, :, :3][mask] = [r2, g2, b2]
    return image

def main(image_path=None):
    images = os.listdir("images")
    f = open("results.txt", "w")

    if image_path is not None:
        images = [image_path]

    for image_name in images:
        im = Image.open("images/"+image_name).convert("RGBA")
        image = np.array(im)

        image = swap_colors(image, (255, 255, 255), (255, 0, 0))

        # create structuring element to determine unconnected groups of pixels in image
        s = scipy.ndimage.morphology.generate_binary_structure(2, 2)

        for i in np.ndindex(image.shape[:2]):
            # skip black pixels
            if sum(image[i[0], i[1]]) == 255:
                continue
            image[i[0], i[1]] = [255, 255, 255, 255]
            # label the different groups, considering diagonal connections as valid
            groups, num_groups = obtain_groups(image, 255, s)
            if num_groups != 1:
                image[i[0], i[1]] = [255, 0, 0, 255]
            # Show percentage
            print((i[1] + i[0]*im.size[0])/(im.size[0]*im.size[1]))

        # Number of red pixels
        red_p = 0
        for i in np.ndindex(image.shape[:2]):
            j = (im.size[1] - i[0] - 1, im.size[0] - i[1] - 1)
            # skip black and white pixels
            if sum(image[j[0], j[1]]) == 255 or sum(image[j[0], j[1]]) == 255*4:
                continue
            image[j[0], j[1]] = [255, 255, 255, 255]
            # label the different groups, considering diagonal connections as valid
            groups, num_groups = obtain_groups(image, 255, s)
            if num_groups != 1:
                image[j[0], j[1]] = [255, 0, 0, 255]
            # Show percentage
            print((j[1] + j[0]*im.size[0])/(im.size[0]*im.size[1]))
            red_p += (sum(image[j[0], j[1]]) == 255*2)

        print(red_p)
        f.write("r_"+image_name+": "+str(red_p)+"\n")

        im = Image.fromarray(image)
        im.show()
        im.save("r_"+image_name)
    f.close()

if __name__ == "__main__":
    if len(sys.argv) == 2:
        main(sys.argv[1])
    else:
        main()

Explanation
Trivial solution. We begin by changing the color of all the white pixels in an image to red. By doing this, it is guaranteed that all the elements (any isle of black pixels) are connected.
Then, we iterate over all the pixels in the image starting from the top left corner and moving right and down. For every red pixel we find we change its color to white. If after this change of color there is still only one element (an element being now any isle of black and red pixels), we leave the pixel white and move on to the next pixel. However, if after the color change from red to white the number of elements is bigger than one, we leave the pixel red and move on to the next pixel.
Update
As it can be seen (and expected) the connections obtained by only using this method show a regular pattern and in some cases, such as in the 6th and 11th images, there are unnecessary red pixels.
This extra red pixels can be easily removed by iterating again over the image and performing the same operations as explained above but from the bottom right corner to the top left corner. This second pass is much faster since the amount of red pixels that have to be checked.
Results
The images which are modified after the second pass are listed twice to show the differences.

Number of red pixels: 18825

Number of red pixels: 334

Number of red pixels: 1352

Number of red pixels: 20214

Number of red pixels: 47268

Number of red pixels: 63 27

Number of red pixels: 17889

Number of red pixels: 259

Number of red pixels: 6746

Number of red pixels: 586

Number of red pixels: 9 1

Number of red pixels: 126

Number of red pixels: 212

Number of red pixels: 683
Score computation:
(1+6746) * (1+126) * (1+259) * (1+17889) * (1+334) * (1+586) * (1+18825) * (1+9) * (1+683) * (1+1352) * (1+20214) * (1+212) * (1+63) * (1+47268) = 1778700054505858720992088713763655500800000 ~ 1.7x10^42
Updated score computation after adding second pass:
(1+ 18825) * (1+ 1352) * (1+ 20214) * (1+ 47268) * (1+ 27) * (1+ 17889) * (1+ 6746) * (1+ 586) * (1+ 1) * (1+ 126) * (1+ 212) * (1+ 334) * (1+259) * (1+683) = 155636254769262638086807762454319856320000 ~ 1.5x10^41

Nice work. Looks like we may need to score this one in scientific notation: 1.7x10^42


text:
Golf range minimum queries of a list
(Inspired by the first problem solved in Stanford’s advanced data structures course.)
Despite the academicese-heavy name, the problem we're going to solve is almost unbearably simple.
We have a list of numbers.
[31, 41, 59, 26, 53, 58, 97]

We're going to cut some contiguous snippet out of that list of numbers.
[31, 41, 59, 26, 53, 58, 97]
    |41, 59, 26, 53|

And then we're going to find the minimum of that snippet. In this case, that's quite obviously 26. That's all.
And the obvious solution is pretty fast, too, with \$O(n)\$ time and \$O(1)\$ space in the size of the list:
minval = arbitrarily large value
for (i=first snippet index, i<last snippet index, i++)
  if (list[i]<minval) minval = list[i]

So what's with the fastest-algorithm?
Where it gets interesting is when you try to see how efficient you can make it when you have a fixed list but a large number of range minimum queries -- snippets to find the minimum of. This version of the problem is useful, for example, if you have a huge time series you want to load only once, but you want to find the minimum of many different subintervals of that time series.
In such a scenario, it would actually be faster in practice to literally precompute all \$n^2/2\$ queries, store it in a table, and then just retrieve data from that table for an \$O(1)\$ time and \$O(n^2)\$ space solution.

^dynamic programming solution taken from the Stanford slides
And then if you're clever enough, you realize that you only have to store each query with size that's a power of two -- you can just combine those power-of-two minima to sum to an arbitrarily sized query, and achieve the same results with constant time and linearithmic rather than quadratic space.
Interestingly, if you keep optimizing, you can get to an \$O(1)\$ time and \$O(n)\$ space solution using a sort of augmented list known as a Fischer-Heun structure. I'd love to go into the details of the structure here, but explaining how it weaves into Cartesian tree building on fixed-size snippets would make this question about 50 pages long. It's explained in the Wikipedia page, however, along with several faster-than-linear intermediate structures.
(If you can get past a research paywall, here's the original Fischer & Heun 2011 paper. And if you’d prefer the much more verbose but much more hand-holdy Stanford lecture style, here are the follow-up slides that goes into this solution, including lots of intermediates.)

The challenge
You can either write a full program or a function that calculates the result of a series of range minimum queries given a fixed list. Scoring is set up such that in general, the shortest and most-efficient-over-lots-of-queries code wins.
Input:
A list of integers xs, followed by a series of i, j pairs denoting the start and end of the snippet, inclusive (so the 26 example above uses indices i=1 and j=4). The list of integers is guaranteed to have at least one integer, and 0 <= i <= j < len(xs). This can be taken in any format that works best for your language — for example, one list for xs and one list of tuples for the i, j pairs; or maybe all the different pairs as a variable number of arguments. For a full program that takes in input from stdin, I’ll fix a format for the input:
xs[0] xs[1] xs[2] xs[3] ...
i1 j1
i2 j2
i3 j3
...

Output:
An ordered collection of the range minima for each i, j query, in the same order that they were given. In case an unordered map (such as a Python dictionary) from each i, j query to its range minimum works better for your language, that will also be allowed as an output; as long as it's obvious which minimum is related to which query.
Once again, for a full program that prints to stdout or a file, I’ll fix the format to have each range minimum on each newline (trailing newlines permitted).
Scoring:
Lower score is better; score is determined by
at(b)^2+as(b)

Where b is the byte count of your code, at is the asymptotic runtime Ө(n) of the algorithm in the size of xs interpreted as a function of n, and as is the asymptotic space usage \$\Theta(n)\$ in the size of xs interpreted as a function of \$n\$. (All constant coefficients in such \$\Theta(n)\$ expressions must be 1, and only the fastest growing term may be kept in expressions, as is standard.)
Therefore, the above pseudocode solution, which uses \$\Theta(n)\$ time and \$\Theta(1)\$ space, and is 126 characters, would have a score of (b => b**2 + 1)(126)=15876+1=15877. (Of course, the pseudocode isn't really valid since it's missing a construct to deal with multiple queries, and also because it's uncompilable pseudocode...)
Test cases:
Input:
31 41 59 26 53 58 97
1 4
0 2
5 6

Output:
26
31
58

Input:
1
0 0
0 0

Output:
1
1

Input:
-4 28 31 -54
0 0
0 1
0 2
0 3
1 1
1 2
1 3
2 2
2 3
3 3

Output:
-4
-4
-4
-4
28
28
-54
31
-54
-54

It seems that we may be as inefficient as we like building a lookup table (since from the description that does not appear to contribute to run-time complexity) and end up with just the values the (i, j) pairs inputted need and then have something which is O(1) in both time and space (since the (i, j) pairs are not n). I feel like I must have misinterpreted something!

I don't understand your scoring system, it seems to depend only on the bytes and size of the input array, but not on the number of queries (nq). What is the score if I call the Ө(n)-time/Ө(1)-space pseudo code nq times in a loop?

You seem to be working with the size of elements of the list being bounded.  This is implied by your "arbitrarily large value" and your "$O(1)$" space complexity in the first bit.  Your algorithm doesn't work if the minimum is larger than the starting value and is $O(m)$ where $m$ is the size of the number.   However it doesn't seem to mention this in the question.  Are the values bounded and if so by what?

Also although it is a common assumption that tables have constant amortized lookup, however in practice (as far as I am aware) there is no known table to actually achieve constant time lookup.  Your question however seems to operate on the assumption that such a table does exist.  How are we to score answers that make use of tables?

@SriotchilismO'Zaic Dictionaries have constant lookup, right? So a table could just be a dictionary with tuples for the keys.

Further explanation of how scoring works would be nice. It's not very clear.

@mbomb007 Dictionaries do not have constant lookup.  They have $O(n)$ with size of the key or worse lookup.  It just tends to be that there is a coefficient that is very close to zero so it is considered not unreasonable to consider it $O(1)$.

@SriotchilismO'Zaic Ah right. Worst case, not average case. Perhaps the OP meant big-theta?

@mbomb007 Hash map dictionaries should be $\Theta(n)$ with the size of the key as well.  This is because the function is required to read all of the key in order to hash it.  Often times this is ignored in more theoretical computer science (just assuming that we have a magic dictionary that can look up things in constant time), but since we do have to write actual code for this question it is important that we understand how to grade answers that make use of hash functions and hash sets.

Jelly, 6 bytes, \$O(n)\$ runtime, \$O(1)\$ space, score 37
r/ịṂɗ€

Try it online!
I hope I’ve understood the scoring correctly. Naive implementation that simply indexes into the list and finds the minimum. Requires no lookup table, but limited by needing to check each input value (so \$O(n)\$ in efficiency.

Python 2, 42 bytes, \$O(n)\$ runtime, \$O(1)\$ space, score 1765
lambda x,L:[min(x[l[0]:l[1]+1])for l in L]

T-SQL 2008, 55 bytes
I apologize for being unable to figure out the scoring system.
Input is 2 tables
SELECT min(x)FROM a,b
WHERE i>f and i<t+2GROUP BY z,f,t

Try it online

source: Stack Exchange

license: Creative Commons - Attribution Share-Alike - https://creativecommons.org/licenses/by-sa/4.0/

sort: votes


text:
Write The Shortest Program To Check If A Binary Tree Is Balanced
For each node in a balanced binary tree, the maximum difference in the heights of the left child subtree and the right child subtree are at most 1.
The height of a binary tree is the distance from the root node to the node child that is farthest from the root.
Below is an example:
           2 <-- root: Height 1
          / \
         7   5 <-- Height 2
        / \   \
       2   6   9 <-- Height 3
          / \  /
         5  11 4 <-- Height 4

Height of binary tree: 4
The following are binary trees and a report on whether or not they are balanced:

The tree above is unbalanced.

The above tree is balanced.
Write the shortest program possible that accepts as input the root of a binary tree and returns a falsey value if the tree is unbalanced and a truthy value if the tree is balanced.
Input
The root of a binary tree. This may be in the form of a reference to the root object or even a list that is a valid representation of a binary tree.
Output
Returns truthy value: If the tree is balanced
Returns falsey value: If the tree is unbalanced.
Definition of a Binary Tree
A tree is an object that contains a value and either two other trees or pointers to them.
The structure of the binary tree looks something like the following:
typedef struct T
{
   struct T *l;
   struct T *r;
   int v;
}T;

If using a list representation for a binary tree, it may look something like the following:
[root_value, left_node, right_node]

May input be empty tree?

In your initial example of a tree, if you remove the leaf 4, is the remaining tree balanced?

No, not that example, I meant the initial one, using ASCII art.

According to my own implementation "C, 117 bytes": No, since the height of the right subarm tree starting from "5" is 2 and the height of the left subarm tree is 0.

Edits are at least 6 chars but please remove the comma from between 'balanced' and 'binary' - 'binary tree' is a noun phrase, so writing 'balanced, binary tree' is the equivalent of 'red, snow mobile' - the comma is not required.

Jelly, 11 bytes
ḊµŒḊ€IỊ;ß€Ạ

Try it online!
The empty tree is represented by [].

Thanks Erik for being amongst the first to answer this question. Jelly certainly is a very popular language on this site. I think I should take the liberty to implement this language. Good to learn from a robust golf-scripting language.

Congrats Erik the Outgolfer, you are winner.

Prolog (SWI), 49 bytes
N+_/B/C:-X+B,Y+C,abs(X-Y)<2,N is max(X,Y)+1.
0+e.

Try it online!
Represents trees as Value/Left_Child/Right_Child, with the empty tree being the atom e. Defines +/2, which outputs through success or failure, with an unbound variable (or one already equal to the tree's height) on the left and the tree on the right--if the height argument is unacceptable, add 9 bytes to define -T:-_+T..
N + _/B/C :-            % If the second argument is a tree of the form _Value/B/C,
    X+B,                % X is the height of its left child which is balanced,
    Y+C,                % Y is the height of its right child which is balanced,
    abs(X-Y) < 2,       % the absolute difference between X and Y is strictly less than 2,
    N is max(X,Y)+1.    % and N is the height of the full tree.
0 + e.                  % If, on the other hand, the second argument is e, the first is 0.

(If the value of each node could be omitted from the input, _/ could be taken out for -2 bytes.)

JavaScript (Node.js), 49 bytes
h=([l,r])=>l?(d=h(l)-h(r))*d<2?1+h(d>0?l:r):NaN:1

Try it online!
-9 bytes by Arnauld.

JavaScript, 58 bytes
h=([l,r])=>l?(l=h(l),r=h(r),m=l>r?l:r,m+m-l-r<2?m+1:NaN):1

Try it online!
Use [] for null, and [left, right, value] for nodes.

Wolfram Language (Mathematica), 50 bytes
f@_[x_,y_]:=f@x&&f@y&&-2<Depth@x-Depth@y<2;f@_=1>0

Use Null for null, value[left, right] for nodes. For example, the following tree is written as 2[7[2[Null, Null], 6[5[Null, Null], 11[Null, Null]]], 5[Null, 9[4[Null, Null], Null]]].
    2
   / \
  7   5
 / \   \
2   6   9
   / \  /
  5  11 4

Try it online!

That's really pretty!

Python 3.8 (pre-release), 133 125 bytes
b=lambda t:((max(l[0],r[0])+1,abs(l[0]-r[0])<2)if(l:=b(t[1]))[1]and(r:=b(t[2]))[1]else(0,0))if t else(0,1)
h=lambda t:b(t)[1]

Try it online!
Takes a tree in the "list" format: A node is [value, left, right] with left and right being nodes.
Invoke the function h.
Returns 0 or False for an unbalanced tree.
Returns 1 or True for a balanced tree.
Ungolfed:
# Returns tuple (current height, subtrees are balanced (or not))
def balanced(tree):
  if tree: # [] evaluates to False
    left = balanced(tree[1])
    right = balanced(tree[2])
    # If  the subtrees are not both balanced, nothing to do, just pass it up
    if left[1] and right[1]:
      height = max(left[0], right[0]) + 1
      subtrees_balanced = abs(left[0] - right[0]) < 2
    else:
      height = 0 # Value doesn't matter, will be ignored
      subtrees_balanced = False
  else:
    height = 0
    subtrees_balanced = True
  return (height, subtrees_balanced)

def h(tree):
  return balanced(tree)[1]

-10: Reversed logic to get rid of nots
If taking arguments in the middle of a call is allowed, this could be shortened to (115 bytes)
(b:=lambda t:((max(l[0],r[0])+1,abs(l[0]-r[0])<2)if(l:=b(t[1]))[1]and(r:=b(t[2]))[1]else(0,0))if t else(0,1))(_)[1]

with _ being the tree to check.

JavaScript, 162 bytes
f=x=>{for(f=0,s=[[x,1]];s[0];){if(!((d=(t=s.pop())[0]).a&&d.b||f))f=t[1];if(f&&t[1]-f>1)return 0;if(d.a)s.push([d.a,t[1]+1]);if(d.b)s.push([d.b,t[1]+1])}return 1}

Try it online!
The format of the input is an object
root={a:{node},b:{node},c:value}

Explanation
for(f=0,s=[[x,1]];s[0];){if(!((d=(t=s.pop())[0]).a&&d.b||f))f=t[1]

Performing breadth first search find the depth of the first node which is missing one or more branches.
if(f&&t[1]-f>1)return 0;if(d.a)s.push([d.a,t[1]+1]);if(d.b)s.push([d.b,t[1]+1])}

Continuing the breadth first search, return zero if any element is two deeper than the depth of the first node missing branches.
return 1}

If no such node is found, return 1

There is probably some way to do the breadth first search better but I couldn't think of it.

I think this fails for some valid cases such as the very first example which should become balanced when you remove the leaf 4.

Julia, 56 bytes
f(t)=t!=()&&(-(f.(t.c)...)^2<2 ? maximum(f,t.c)+1 : NaN)

With the following struct representing the binary tree:
struct Tree
    c::NTuple{2,Union{Tree,Tuple{}}}
    v::Int
end

c is a tuple representing the left and right nodes and the empty tuple () is used to signal the absence of a node.
Falsey value is NaN, any integer is truthy.

Assuming the encoding is UTF-8, this is actually 57 bytes because of the ≢, according to TIO's built-in byte counter. Anyhow, welcome to CG&CC!

Yes, you're right. I corrected it, so that it's now actually 56 bytes

Kotlin, 67 bytes
fun N.c():Int=maxOf(l?.c()?:0,r?.c()?:0)+1
fun N.b()=l?.c()==r?.c()

Where
data class N(val l: N? = null, val r: N? = null, val v: Int = 0)

Try it online!

Python 2, 99 96 94 bytes
lambda A:A==[]or(abs(D(A[1])-D(A[2]))<2)*f(A[1])*f(A[2])
D=lambda A:A>[]and-~max(map(D,A[1:]))

Try it online!
3 bytes from Jo King.
Takes input as: empty node is [], and other nodes are [<value>, <leftNode>, <rightNode>]. Outputs 0/1 for False/True.

C, 117 bytes
h(T*r){r=r?1+h(h(r->l)>h(r->r)?r->l:r->r):0;}b(T*r){return r->l&&!b(r->l)||r->r&&!b(r->r)?0:abs(h(r->l)-h(r->r))<=1;}

Struct implementation is the following:
 typedef struct T
    {
        struct T * l;

        struct T * r;

        int v;

    } T;

Try This on JDoodle

This appears to be 117 bytes, though you can do <2 for that last check instead

Also, I'm not sure how valid this is, since it relies on a data structure defined outside of the submission

all_licenses: Creative Commons - Attribution Share-Alike - https://creativecommons.org/licenses/by-sa/4.0/


text:
Determine Beckett Grading Service (BGS) Final Grade
Introduction:
Beckett Grading Service (aka BGS), is a company which encapusulates and grades Trading Card Game (TCG) cards (i.e. sport cards, Magic The Gathering, Pokémon, Yu-Gi-Oh, etc. etc.). The TCG cards that come in for grading will be checked on four categories (which we will later refer to as subgrades): centering, corners, edges, and surface. These subgrades will be in the range \$[1, 10]\$ in increments of \$0.5\$. Based on these four subgrades, the graded card is also given a final grade (in the same range). This final grade isn't just an average however. To quote the BGS FAQ page (minor note when reading: the grading level/point they're talking about is a step of 0.5):

Here is how we do it!
The overall numerical grade is not a simple average of the four report card grades. Beckett Grading Services uses an algorithm which determines the final grade using the 4 sub grades on the front label of the card holder. The lowest overall grade is the first category to observe because it is the most obvious defect, and the lowest grade is the most heavily weighted in determining the overall grade.
Example :
Centering = 9.5; Corners = 9.5; Edges = 9; Surface = 8
Final grade = 8.5
The reason that this card received an 8.5 is that even though the Surface grade was an 8 (the lowest grade overall), the 9.5 grades on Centering and Corners were strong enough to bring it up a full point to reach the 8.5 level.
Another example :
Centering = 9.5; Corners = 9.5; Edges = 8.5; Surface = 9
Final grade = 9
Upon first glance, it may appear that this card should've received a grade different than a 9. The most this card could receive was .5 (or one-half grade) above the lowest sub-grade. The Edges were the lowest in this case, hence, the card received the overall 9 grade. Even though Centering and Corners received grades of 9.5, a key point to remember is that the minimum requirement to receive a grade of Gem Mint is to have at least three grades of 9.5 and the fourth to be no less than a 9.
Also, please note that the final grade rarely, if ever, exceeds two levels above the lowest of the four characteristic grades. For example, if a card has characteristic grades of Centering 10, Corners 6, Edges 10 and Surface 10, the final grade will be a "7" (of which is exactly two grading levels above the lowest characteristic grade).

Although the algorithm they use to determine the final grade has never been revealed, someone was able to come up with a set of rules as algorithm for determining the final grade based on the four subgrades by looking at as many example graded cards as possible (source):
In pseudo-code:
Sort subgrades in decreasing order

var fourth = fourth number in this order # so the lowest subgrade
var third = third number in this order   # so the second to last subgrade - could be equal
                                         #  to the lowest of course)
var first = first number in this order   # so the highest subgrade
var diff = third - fourth                # difference between these two lowest subgrades
var finalGrade                           # our final grade we're calculating

If diff == 0:
  finalGrade = fourth
Else:
  If fourth is Edges OR fourth is Surface:
    If diff < 1:
      finalGrade = fourth + 0.5
    Else:
      If (diff == 1 AND fourth + 1 >= 9.5)
         OR (first - fourth == 1.5 AND first != 10):
        finalGrade = fourth + 0.5
      Else:
        finalGrade = fourth + 1.0
  Else-if fourth is Centering:
    If diff < 2:
      finalGrade = fourth + 0.5
    Else-if diff < 4:
      finalGrade = fourth + 1.0
    Else:
      finalGrade = fourth + 1.5
  Else(-if fourth is Corners):
    If diff < 3:
      finalGrade = fourth + 0.5
    Else:
      finalGrade = fourth + 1

Some examples:

9.5 8.5 7.5 9.5 = 8.5: fourth number is Edges (7.5); diff is 1.0. So the final grade becomes fourth + 1.0.
10 10 10 8.5 = 9.5: fourth number is Surface (8.5); diff is 1.5. So the final grade becomes fourth + 1.0 (with a different if-else path than the one above)
9.5 9.5 9 8 = 8.5: fourth number is Surface (8); diff is 1.0; first number is 9.5. So the final grade becomes fourth + 0.5.
5.5 8.5 9 7 = 6: fourth number is Centering (5.5); diff is 1.5. So the final grade is fourth + 0.5.
5 7 9 9.5 = 6: fourth number is Centering (5); diff is 2. So the final grade is fourth + 1.
4.5 9 9.5 9 = 6: fourth number is Centering (4); diff is 4.5. So the final grade is fourth + 1.5.
9 6.5 9 9.5 = 7: fourth is Corners (6.5); diff is 2.5. So the final grade is fourth + 0.5.

In summary: Corners is punished hardest, Centering next, Surface/Edges the least. How much the overall grade is better than the worst subgrade depends on which subgrade is the worst, and also depends on how much the other three subgrades are better than the worst subgrade, measured by diff.
Here an ungolfed reference implementation of the described algorithm in Java. (It can also be used to generate more random test cases; see the bottom section of the output.)
Challenge:
Given the four subgrades as input, output the final grade based on the algorithm above.
Challenge rules:

Input may be in any reasonable format. Can be four separated inputs. A sorted list. Etc. Make sure to state the order of the subgrades centering, corners, edges, surface you've used. Inputs can be as decimal numbers, strings, or other reasonable input-types.

If your language doesn't have a numeric type that allows decimal values, or if you choose to, you are allowed to take all inputs multiplied by 10 as integers (as long as it's consistent). I.e. instead of 4.5 8 9.5 6.5 you could also take the input as 45 80 95 65. In which case the output is of course similar. Make sure to mention in your answer if you're using integers which are multiplied by 10!

Output may be returned by a function in any reasonable type, output to STDOUT/STDERR/Exit Code, etc.
Of course you don't have to follow the algorithm exactly as described above. As long as it gives the correct result for any of the 130321 possible test cases, it's fine to implement it in any way you'd like. If you think hard-coding all possible test cases is shorter in your language of choice, by all means go for it.

General rules:

This is code-golf, so shortest answer in bytes wins.
Don't let code-golf languages discourage you from posting answers with non-codegolfing languages. Try to come up with an as short as possible answer for 'any' programming language.
Standard rules apply for your answer with default I/O rules, so you are allowed to use STDIN/STDOUT, functions/method with the proper parameters and return-type, full programs. Your call.
Default Loopholes are forbidden.
If possible, please add a link with a test for your code (i.e. TIO).
Also, adding an explanation for your answer is highly recommended.

Test cases:
Centering  Corners  Edges  Surface  =  Final grade
10         10       10     10       =  10
10         10       10     9.5      =  10
10         10       9.5    10       =  10
10         9.5      10     10       =  10
9.5        10       10     10       =  10
10         9.5      9.5    9        =  9.5
8.5        9        10     9.5      =  9
8          8        8.5    8.5      =  8
9.5        8.5      9.5    9.5      =  9
5.5        7.5      7.5    7        =  6
9.5        8.5      8.5    9        =  8.5
8          8.5      9      9        =  8.5
9.5        4.5      4.5    2.5      =  3.5
9.5        6        6.5    6        =  6
9.5        7        7      8        =  7
9.5        9.5      9.5    8.5      =  9
7.5        8.5      9      8        =  8
9          8        7      8.5      =  8
10         9.5      9.5    8        =  9
10         9        9.5    9        =  9
10         8        9      9        =  8.5
9.5        8.5      7.5    9.5      =  8.5
9.5        8.5      8.5    6.5      =  7.5
10         10       10     8.5      =  9.5
9.5        9.5      9      8        =  8.5
5.5        8.5      9      7        =  6
5          7        9      9.5      =  6
4.5        9        9.5    9        =  6
9          6.5      9      9.5      =  7
1          1        1      1        =  1
1          10       10     10       =  2.5
10         1        10     10       =  2

Can we use grade points (i.e. multiply by 2) instead?

@NickKennedy No sorry, either the decimal values or times 10 as integer. Someone asked me whether we could take the decimals as times 2 or times 10 in the Sandbox, but I feel times 2 makes the values a bit too different. Times 10 is fine though, since the in-/output values are close to the intended decimal values, and it would allow languages without decimals to compete as well.

Charcoal, 73 71 bytes
≦⊗θ≔⌊θη≔⁻⌊Φθ⁻κ⌕θηηζＩ⊘⁺η∧ζ⎇⌕θη∨⎇⊖⌕θη∨›²ζ∨∧⁼²ζ‹¹⁶η∧⁼³⁻⌈θη›²⁰⌈θ›⁶ζ²⊕⌊⟦²÷ζ⁴

Try it online! Link is to verbose version of code. Takes input as an array of 4 values [Centring, Corners, Edges, Surface]. Explanation:
≦⊗θ

Double all the values so we're dealing in grade points rather than grades.
≔⌊θη

Find the worst grade.
≔⁻⌊Φθ⁻κ⌕θηηζ

Find the worst of the other grades, i.e. the second worst, and subtract the worst grade.
Ｉ⊘

Halve the final value and cast to string for display.
⁺η

Add the bonus to the minimum grade.
∧ζ

No bonus if the worst two grades are the same.
⎇⌕θη

Check whether the worst grade was for Centring.
∨⎇⊖⌕θη∨›²ζ∨∧⁼²ζ‹¹⁶η∧⁼³⁻⌈θη›²⁰⌈θ›⁶ζ²

If not then check whether only one bonus point should be awarded otherwise award two bonus points. (If the worst grade was for Corners then add a bonus point if the difference was less than 6 grade points or 2 if it was 6 or more. The calculation for Edges and Surface is longer but still only adds one or two bonus points.)
⊕⌊⟦²÷ζ⁴

If the worst grade was for Centring then add a bonus point for every 4 grade points between the worst two grades up to 2, plus a further bonus point.

Perl 5 (-ap), 157 154 bytes
minor improvements !$d?0:... inverted to $d?...:0, >=9.5 changed to >9.4 and !=10 to <10
($w,$x,$y,$z)=sort{$b<=>$a}@F;$d=$y-$z;$_=$z+=$d?$z==$F[2]|$z==$F[3]?$d<1?.5:$d==1&$z+1>9.4|$w-$z==1.5&$w<10?.5:1:$z==$F[0]?$d<2?.5:$d<4?1:1.5:$d<3?.5:1:0

TIO
first answer was
($w,$x,$y,$z)=sort{$b<=>$a}@F;$d=$y-$z;$_=$z+=!$d?0:$z==$F[2]|$z==$F[3]?$d<1?.5:$d==1&$z+1>=9.5|$w-$z==1.5&$w!=10?.5:1:$z==$F[0]?$d<2?.5:$d<4?1:1.5:$d<3?.5:1

straight forward, ungolfed
!$d?0
:$z==$F[2]|$z==$F[3]?
    $d<1?.5
    :$d==1&$z+1>=9.5|$w-$z==1.5&$w!=10?.5
    :1
:$z==$F[0]?
    $d<2?.5
    :$d<4?1
    :1.5
:$d<3?.5
:1

TIO

JavaScript (V8), 160 159 154 151 bytes
s=>{[C,O,E,S]=[...s];[a,b,c,d]=s.sort((a,b)=>b-a);e=c-d;return(e?d==E|d==S?e<1?.5:(e==1&d>8.4)|(a-d==1.5&a<10)?.5:1:d-C?e<3?.5:1:e<2?.5:e<4?1:1.5:0)+d}

Try it online!
A bit ungolfed:
function calculateScore(score) {
  [center,corner,edge,surface] = [...score]; // makes a copy because sort is in-place
  [first,second,third,fourth] = score.sort((a,b)=>b-a); // standard sort is alphabetically, this will sort by value
  diff = third - fourth;
  return fourth + (// Every final score is fourth + something else
    diff != 0
      ? fourth == edge || fourth == surface
        ? diff < 1
          ? 0.5
          : (diff == 1 && fourth >= 8.5) || (first-fourth == 1.5 && a < 10)
            ? 0.5
            : 1
      : fourth != corner
        ? diff < 3
          ? 0.5
          : 1
        : diff < 2
          ? 0.5
          : diff < 4
            ? 1
            : 1.5
      : 0 // diff == 0
  );
}

-1: >=8.5 -> >8.4
-5 thanks to @Kevin Cruijssen
-3 for ES6 copying with [...s]

I'm not too skilled with JS, so I'm sure more can be golfed, but some basic golfs like ||to | and && to & can be applied, as well as return(...)+d instead of return d+(...) to save 5 bytes: Try it online.

Also, if you haven't seen them yet, tips for golfing in JavaScript and tips for golfing in  might be interested to read through. :)

Also, I think you can remove the O and b in the input/sort: s=>{[C,,E,S]=[...s];[a,,c,d], since you're not using the b in the rest of the code.

135

JavaScript (ES6),  115 ... 105  102 bytes
Takes input as an array.
a=>([w,d,,b]=([C,c]=a).sort((a,b)=>a-b),(d-=w)?(w-C?w-c?d<1|(w>8?d:b-w-.5)==1:d<3:d<4?d<2:3)||2:0)/2+w

Try it online!
or compare all results with those of the ungolfed reference implementation
Commented
a => (                      // a[] = [ centering, corners, edges, surface ]
  [ w,                      // w = worst grade (4th grade)
    d,                      // d = 3rd grade
    ,                       // the 2nd grade is ignored
    b                       // b = best grade (1st grade)
  ] =                       //
    ([C, c] = a)            // before sorting, save C = centering and c = corners
    .sort((a, b) => a - b), // sort the grades from lowest to highest
  (d -= w) ?                // d = 3rd grade - worst grade; if it's not 0:
    ( w - C ?               //   if the worst grade is not for centering:
        w - c ?             //     if the worst grade is not for corners:
          d < 1 |           //       yield 1 if d < 1 OR
          ( w > 8 ?         //       either
              d             //         d (if w > 8)
            :               //       or
              b - w - .5    //         b - w - 0.5 (if w <= 8)
          ) == 1            //       is equal to 1
        :                   //     else (worst grade = corners):
          d < 3             //       yield 1 if d < 3
      :                     //   else (worst grade = centering):
        d < 4 ?             //     if d < 4:
          d < 2             //       yield 1 if d < 2
        :                   //     else:
          3                 //       yield 3
    ) || 2                  //   if the above result is 0, yield 2 as the default value
  :                         // else (d = 0):
    0                       //   yield 0
) / 2 + w                   // half the above result and add it to w

Python 3, 132 bytes
def f(a):c,C,*_=a;w,x,y,z=sorted(a);d=x-w;return(x>w)*[[2-(d<1or d==1<w-7or z-w==1.5>z-8.5),3-(d<2)-(d<4)][w==c],-~(d>=3)][w==C]/2+w

Try it online!

C# (Visual C# Interactive Compiler), 164 bytes
(a,b,c,d)=>{int[]e={a,b,c,d};Array.Sort(e);int f=e[0],h=e[1]-f,i=e[3];return(h<1?0:f==c|f==d?h<9?5:h<11&f>84|i-f==15&i<96?5:10:f==a?h<20?5:h<40?10:15:h<30?5:10)+f;}

Try it online!


text:
Decode a variable-length quantity
A variable-length quantity (also referred to as VLQ or uintvar) is a way to encode up to a 28 bit integer value using only as many bytes as necessary. This was used in MIDI file format as a way to minimize the size of certain event data.
The way it works is fairly simple.  As a big-endian series of bytes, the most significant bit (MSB) of each byte is a 1 to indicate that another VLQ byte follows. The remaining 7 bits of each byte make up the decoded value.
Example (from Wikipedia):
[ 0x86, 0xc3, 0x17 ] => 106903

Additional references: Wikipedia.
Challenge:
Given a variable-length quantity, convert it to it's integer value.
Input:
A list of one to four bytes or a 32-bit value type representing a valid VLQ of an integer.
Output:
The integer value of the VLQ input.
Rules and scoring:

This is code-golf, so shortest answer in bytes for each language wins.
Standard rules and default I/O rules apply.
Loopholes forbidden (of course).
Please provide link with a test for your code (TIO.run, etc).
A clear explanation for your answer is highly recommended.
Built-ins that handle this conversion are not banned, however not using them is a lot more interesting.

Test cases:
Input (VLQ)                   Output (int)

[                   0x00 ] => 0
[                   0x07 ] => 7
[                   0x7f ] => 127
[             0x81, 0x00 ] => 128
[             0xC0, 0x00 ] => 8192
[             0xff, 0x7f ] => 16383
[       0x81, 0x80, 0x00 ] => 16384
[       0x86, 0xc3, 0x17 ] => 106903
[       0xbd, 0x84, 0x40 ] => 1000000
[       0xff, 0xff, 0x7f ] => 2097151
[ 0xC0, 0x80, 0x80, 0x00 ] => 134217728
[ 0xFF, 0xFF, 0xFF, 0x7F ] => 268435455

Note: you are not required to use hex literals to represent a byte as your input or output. You may use decimal literal ([ 129, 128, 0 ]), integer (0x80818000) or any other reasonable byte/octet representation if better suited to your platform.  Format is flexible as long as it represents 1-4 byte/octets.
Golf away!

Is the last byte of the input guaranteed to be < 128? Or do we need to support cases such as [0x01, 0x80, 0x02] => 1?

Inputs are guaranteed to be valid VLQ values, so last byte will always be MSB = 0.

@Arnauld I see better now what you were getting at, and in retrospect the case you mention would have been good to have required.  In MIDI, for example, you'll be reading a stream of data and so you don't necessarily know which byte is the last one.  The way it is written, guaranteeing that the last byte in the list is the last byte in the VLQ makes the MSB irrelevant and in fact kind of defeats the purpose of VLQ. As in, you wouldn't necessarily be able to use these (valid for the challenge) routines for MIDI file decoding. Totally my bad! Should have kept this in sandbox much longer...

This is partly my bad as well because I think I've seen the challenge in the sandbox and didn't pay enough attention. But yes, that's what I had in mind: given a list of bytes, either output the first VLQ value or output all VLQ values.

I like this challenge, though it may have been more interesting if the input was an arbitrary number of bytes, and you had to return a list of values (of different length than the input).  This would have tested the "variable length" aspect of the encoding.

Can we take the input-list in reversed order?

@KevinCruijssen, a variable length quantity is supposed to be a stream of bytes, where technically you only know when it ends by reaching the MSB=0 marker.  I know the rules didn't quite capture that, but by the definition of VLQ I'm going to have to say no.

@gwaugh Ok, that makes sense and I can understand the reasoning. I'll modify my MathGolf answer accordingly. :)

do we need to handle garbage bytes before or after the VLQ ends?

so if we use a 32 bit int as input can we pad the other bytes with zeros? It's not clear to me how input is taken. From your example 0x818000 it looks like the rest is zero padded.

@qwr, I see what you mean.  In the case of taking input as a 32 bit integer then my example is incorrect, it should be 0x80818000.  I will update.

APL (dzaima/APL), 8 bytes
128(⊣⊥|)

Try it online!
How:
128(⊣⊥|) ⍝ Anonymous function
128   |  ⍝ Input modulo 128
    ⊣⊥   ⍝ Decoded from base 128

Pari/GP, 24 bytes
a->fromdigits(a%128,128)

Try it online!

J, 10 bytes
128#.128|]

Try it online!
Taking inspiration from J Salle's APL answer.

128|] Remainder of the input numbers divided by 128
128#. Interpreted as the digits of a base 128 number

Wolfram Language (Mathematica), 25 bytes
Fold[128#+#2&,#~Mod~128]&

Try it online!
Wolfram Language (Mathematica), 25 bytes
#~Mod~128~FromDigits~128&

Try it online!

Jelly, 6 bytes
%Ø⁷ḅØ⁷

Try it online!
Equivalent to alephalpha's Pari/GP answer.
Method: Given \$n\$, output \$n \mod 128\$, converted from base \$128\$ to decimal.

05AB1E, 6 bytes
žy%žyβ

Try it online!
ಠ_ಠ Why do both Jelly and 05AB1E have 2-byte constants for 128? Well... It's \$2^7\$. But surely those bytes could be used for something better, right?

Yeah, that builtin is useless nowadays. In the legacy version I could see some use for it, only when you have a digit before it in your program. Otherwise you could just use 7o. Nowadays you can compress certain 3-byte integers (range [101,355]) in 2 bytes though, so 128 can be ƵR anyway.. I've also wondered the same thing about the 2-byte builtin for 16.. Usually you'd just use the literal, or otherwise we'd have 4o/4n/8· if a digit is behind it in the program. Only when a digit is before the 16, which I don't think would happen, the builtin is useful..

Python 3, 58 49 bytes
-9 bytes thanks to @Chas and @ar4093
lambda x:int("".join(bin(a|128)[3:]for a in x),2)

Try it online!
or
lambda x:int("".join(f"{a%128:07b}"for a in x),2)

Try it online!
Input via list of integers.
Python's bin function adds "0b" to the beginning of the string, so those have to be taken off before they can be concatenated. It also doesn't keep leading zeros, so if there aren't any (aka the last byte) those have to be added back in. And if there is a leading one (aka all but the last byte) that must be removed as well. Thanks to @Chas for figuring out that by always setting the first bit, I can just remove the first three characters and be done.
Apparently (according to @ar4093) the format function allows not only not having the '0b' prefix, but also removing the first bit and padding to 7 characters all at the same time.

Welcome to CGCC, which will make you a much worse programmer! :). I had the same thought as you at first. You can save 9 bytes with bin(a|128)[3:] as you then don't need the zfill.

As you said, with the format function you can save 9 bytes: bin(a)[2:].zfill(8)[1:] --> f"{a%128:07b}"

Stax, 8 bytes
å→♂á╣>nt

Run and debug it
Algorithm:

Convert each byte to fixed-width 7 bit array.
Flatten bit arrays into one.
Convert bit array back to number.

JavaScript (ES6), 29 bytes
-2 bytes thanks to @Shaggy
Takes input as an array of bytes.
a=>a.map(p=c=>p=p<<7|c&127)|p

Try it online!

APL+WIN, 22 bytes
Prompts for a vector of integers:
2⊥¯32↑,0 1↓⍉(8⍴2)⊤¯4↑⎕

Try it online! Courtesy of Dyalog Classic
Explanation:
¯4↑⎕ Pad the vector to 4 integers from the right.

⍉(8⍴2)⊤ Convert to a matrix of 8 bit values.

,0 1↓ drop the MSBs and flatten to a vector.

2⊥¯32↑ pad bit vector to 32 bits starting at LSB and convert back to integer.

Stax, 12 bytes
ü╫ôà¡k2Wù}a☺

Run and debug it at staxlang.xyz!
Unpacked (14 bytes) and explanation:
rk128%128i^#*+
r                 Reverse 'cuz big-endian
 k                Fold from the left using block:
  128%              Modulize by 128
      128i^#        Push 128 to the power of (one plus the iteration index)
            *       Multiply
             +      Add to the total
                  Implicit print

Stax has builtin base conversion, but it only works on strings. It almost works on lists of integers, though; the problem is in Stax's handling of 0.
A string is a list of integers. When you're using such a list as a string, any zeroes are automatically converted to 32 as a nice shorthand for spaces. Since the builtin |b for base conversion treats its operand as a string rather than as a raw list of integers, any case with a zero will fail.
10 bytes, fails on zeroes
Ç┘_A♥∙QZ►╣
{128%m128|b    Unpacked
{128%m         Modulize each by 128
      128|b    Convert from base 128

Run and debug it at staxlang.xyz!

I see where that issue came from.  {:B7)m$:b packs to 8, and seems to work as well, although it's kind of an exotic use of$.

@recursive That's clever. Post it as a separate answer.

Done.  https://codegolf.stackexchange.com/a/189554/527

C (gcc), 48 bytes
Takes an integer in big-endian order as input, which is the same order as a byte array.
f(i,j){for(j=0;j|=i&127,i&128;j<<=7,i>>=8);i=j;}

Try it online!
C (gcc), 53 bytes
If a byte array is needed:
f(c,j)char*c;{for(j=0;j|=*c&127,*c++&128;j<<=7);c=j;}

Try it online!

When I compile your TIO testing code with -Os it only handles the last two cases. I don't know enough C to tell why.

@qwr TIO defaults to -O0, which allows you to (usually) store a return value into the first parameter. This is a quirk^Wfeature in code golfing, but doesn't work with higher optimization levels.

You can shave off a byte by replacing &128 with >>7.

MathGolf, 14 13 bytes
♣%hrx♣▬^mÅε*Σ

Input as a list of integers.
Try it online.
I have the feeling this can be shorter.. It's a bit annoying that MathGolf has a 1-byte builtin for the constant 128, but no base-conversion (except for binary/hexadecimal).
Explanation:
♣%            # Take modulo-128 on each value in the (implicit) input-list
  h           # Push the length of the list (without popping the list itself)
   r          # Pop and push a list in the range [0, length)
    x         # Reverse the list to (length, 0]
     ♣▬       # Take 128 to the power of each value in this list
       ^      # Zip the two lists together to create pairs
        mÅ    # Map each pair to (using the following two commands):
          ε*  #  Reduce by multiplication (so basically the product of the pair)
            Σ # After multiplying each pair, take the total sum
              # (after which the entire stack joined together is output implicitly)

Base conversion has been on the table since day 1, but there are some other overhauls regarding base conversion that I want to address at the same time. For example, I don't want to different operators to convert to/from hexadecimal strings.

Japt, 10 8 bytes
Takes input as an array of integers.
muIÑ ìIÑ

Try it or run all test cases (header in both converts from input format used in challenge)
Saved 2 bytes by taking inspiration from alephalpha's solution.
muIÑ ìIÑ     :Implicit input of integer array
m            :Map
 u           :  Modulo
  I          :    64
   Ñ         :    Multiply by 2
     ì       :Convert to decimal
      IÑ     :  From base 64*2

PHP, 42 bytes
foreach($argv as$a)$r=$r<<7|$a&127;echo$r;

Try it online! and verify all test cases.
Input via command line args, output to STDOUT.

Charcoal, 11 bytes
Ｉ↨﹪θ¹²⁸¦¹²⁸

Try it online! Link is to verbose version of code. Takes input as an array. Explanation:
   θ        Input array
  ﹪ ¹²⁸    Elementwise modulo 128
 ↨      ¹²⁸ Convert from base 128
Ｉ          Cast to string for implicit print

Python 2, 42 bytes
f=lambda a:a>[]and a[-1]%128+f(a[:-1])*128

Try it online!

Windows Batch, 76 bytes
:a
@set/ax="%1&127",y=y*128+x
@if %2. neq . @shift&goto:a
@echo %y%
Pass parameters prefixed with "0x" and space between (e.g. 0xC0 0x80 0x80 0x00).

Nicely done. Obviously for anyone else testing it, you'll need to make sure to an do a @set y=,ax= between runs.

just the "set y=" is enough.

ARM Thumb-2 machine code, 16 bytes
00 02 c0 01 04 c9 52 06 00 eb 52 60 f9 d2 70 47

Assembler source with pseudo-C:
    .syntax unified
    .arch armv7-a
    .globl vlq
    .thumb
    .thumb_func
    // Input: r1: array of **uint32_t** containing VLQ bytes
    // Output: r0
vlq:
    // initial accumulator
    movs    r0, #0                       @ acc = 0;
.Lloop:                                  @ do {
    // Shift accumulator for next value
    lsls    r0, r0, #7                   @    acc = acc << 7;
    // Load next vlq byte, advance ptr
    ldmia   r1!, {r2}                    @    tmp = *ptr++;
    // Shift left so the continuation
    // bit is in the carry
    lsls    r2, r2, #32 - 7              @    carry = tmp & 0x80;
                                         @    tmp = tmp << (32 - 7);
    // Insert it into the accumulator,
    // not affecting the flags (wide)
    add.w   r0, r0, r2, lsr #32 - 7      @    acc = acc + (tmp >> (32 - 7));
    // If there is no carry, loop again
    bcs     .Lloop                       @ } while (carry);
    // Return
    bx      lr                           @ return acc;

This function can be called from C with a dummy parameter to place data in r1.
uint32_t vlq(uint32_t dummy, const uint32_t *data);
// Note: uint32_t, not uint8_t
const uint32_t vlqs[] = { 0x86, 0xC3, 0x17 };
printf("%u\n", vlq(314159 /* dummy */, vlqs));

Ruby, 40 bytes
->a{a.map{("%08b"%_1)[1..]}.join.to_i 2}

Attempt This Online!

J-uby, 40 bytes
Port of my Ruby answer, just for fun.
:*&(:%&"%08b"|~:[]&(1..))|:join|~:to_i&2

Attempt This Online!
Explanation
:* & (           # Map with…
  :% & "%08b" |  #   Format (%) as binary padded to 8 digits, then
  ~:[] & (1..)   #   take range of characters 1..end
) |              # then
:join |          # join, then
~:to_i & 2       # convert to int as base 2

Nibbles, 5 bytes (10 nibbles)
`@;128.@%

`@;128.@%$@  # = program with implicit arguments added
`@           # convert from base
   128       # 128
  ;          # (and save that value)
       @     # the input values
      .      # each
        %$   # modulo
          @  # the saved value of 128


text:
Radioactive Bit Checking
Note: This is the radiation-hardening version of my previous challenge Pristine Bit Checking. This should be much more difficult than that one.
Write a program/function that takes two integers in the range \$0\$ to \$255\$ inclusive, and returns whether the binary forms of the numbers are exactly one bit different.
For example, \$1\$ and \$0\$ have binary forms 00000001 and 00000000, which are one bit apart. Similarly, \$152\$ and \$24\$ are 010011000 and 000011000, so they return true.
However, your code must be radiation-hardened, such that if any one bit in your program is flipped, it should still work correctly. For example, if your program was the single byte a (01100001), then all the 8 possible modified programs:
á ! A q i e c `

must still work correctly. Make sure you are modifying by bytes (e.g. the á up there is actually representing the byte \$225\$, not the actual two byte character á).
Test cases:
With Truthy meaning they are different by one bit.
0,1     => Truthy
1,0     => Truthy
152,24  => Truthy
10,10   => Falsey
10,11   => Truthy
11,12   => Falsey
255,0   => Falsey

Rules:

Provide a testing framework that can verify that your program is properly radiation-hardened, since there will be a lot of possible programs (number of bytes*8), or else a complete proof of validity.

Please make sure to double-check your program is valid before you post it.

Output can to be either truthy/falsey (either way around is fine), or else as one distinct value for truthy and the rest as falsey

This can be through any of the Standard input/output methods

Here is a helper program that can be used to produce all the variations of an inputted program.

"should be much more difficult than that one" -- this is stating the matter lightly

For the record, ignoring empty programs, this would be impossible in any language whose code must be valid UTF-8.

HTML + JavaScript, 210 bytes
<script>[d,b]=location.search.match(/[\d]([\d]*)?/g);1/b/d&&document.write([i=b^d][i--&i]+'<!-'+'-')</script></script>0<script>[d,b]=location.search.match(/\d+/g);document.body.innerHTML=[i=b^d][i&i-1]</script>

Open page with search parameters index.html?a=1&b=2.
Validation (Python + Selenium + Firefox)
# -*- coding: utf-8 -*-
from selenium import webdriver
import os
source = "<script>[d,b]=location.search.match(/[\d]([\d]*)?/g);1/b/d&&document.write([i=b^d][i--&i]+'<!-'+'-')</script></script>0<script>[d,b]=location.search.match(/\d+/g);document.body.innerHTML=[i=b^d][i&i-1]</script>"
filename = os.path.abspath("temp1.html")

browser = webdriver.Firefox()

def test(html, input_values, expected):
    with open(filename, "w", encoding="latin-1") as html_file:
        html_file.write(html)
    [a, b] = input_values
    browser.get("file:///" + filename + "?a=" + str(a) + "&b=" + str(b))
    text = browser.find_element_by_tag_name("body").text
    if text == "true" and expected == True: return True
    if text == "false" and expected == False: return True
    if text == "undefined" and expected == False: return True
    try:
        if int(text) != 0 and expected == True: return True
        if int(text) == 0 and expected == False: return True
    except: pass
    print(html, input_values, expected, text)

testcases = [
    [[1, 1], False],
    [[1, 2], False],
    [[1, 3], True],
]

fullTestcases = [
    [[0, 1], True],
    [[1, 0], True],
    [[152, 24], True],
    [[10, 10], False],
    [[10, 11], True],
    [[11, 12], False],
    [[255, 0], False],
]

def runAllTestcases(html, testcases):
    for testcase in testcases:
        test(html, *testcase)

runAllTestcases(source, fullTestcases)

for pos in range(len(source)):
    for flip in range(8):
        flip_char = chr(ord(source[pos]) ^ (2 ** flip))
        flip_source = source[0:pos] + flip_char + source[pos+1:]
        runAllTestcases(flip_source, testcases)
    print(pos, "/", len(source))

browser.quit()

Usage:

make sure you have python3 and firefox installed.
save this python code to your locale.
pip install selenium
download firefox web driver and put it in the same folder of this python source.
execute this validator and wait it finish.

How
If the modification is on the first time of script. The script will throw some errors (syntax error, undefined variable, etc.). And the second time of script will run correctly and overwrite the output.
If the modification is not on the first time of script. The script output

Learn some (useless) HTML knowledge

document.write insert HTML to current position. You should not output unbalanced HTML in usual. When unbalanced HTML is printed. It inserted as is. And following HTML are re-parsed. You may even insert something like <!-- to open a comment.
When trying to parse <head>, if HTML parser got anything should not be existed here, the <head> tag is closed immediately and a <body> is created.
When the  tag is created, document.body is accessible.
<script> tag is closed by </script>. Anything between them are script. Script may not be valid, and that doesn't matter (doesn't break HTML).
Exception in previous <script> tag does not prevent the following 's execution.

There must be some more golfing spaces. But I am tired to wait the validator...


text:
Black and white shirts
This puzzle is based on this Math.SE post. A more complex version of this problem can be found over here.
Assume I have some number of black shirts and some number of white shirts, both at least 1. Both colors of shirt have a non-zero durability. All shirts of a given color start with the same durability.
Every day, I pick out a clean shirt to wear, and it becomes dirty. Once I run out of all clean black shirts or all clean white shirts, I wash all my dirty shirts of both colors and start over. Clean shirts do not get washed. Whenever a shirt gets washed, its durability goes down by one. Immediately after washing, if the durability of a shirt reaches 0, it must be thrown out.
When picking which shirt to wear of a particular color, I always choose a shirt with the highest durability of that color to ensure even wear and tear among shirts.
Challenge:
Take in a sequence of two characters of infinite length (eg. b b b w b w w b...) representing my choice of shirt to wear on that day. Continue execution until either my last black shirt or my last white shirt is thrown out. Once this occurs, stop consuming input and halt execution immediately. Note that the program must not consume any more input than is required before halting.
Inputs:
Number of black shirts, number of white shirts, durability of black shirts, durability of white shirts, and an infinite number of two single characters, your choice (eg. b and w)
Note: if your language of choice does not support reading input of an infinite length (i.e. from a stream), assume an input of arbitrary length that is at least long enough for one color of shirt to run out.
Output:
None. The program must simply immediately halt when the last shirt of either color is thrown away.
Note: if your language does not support reading input of infinite length, then instead you should output the number of characters processed.
Test cases:
The following test cases represent the amount of input the program should process before halting. The sequence of w’s and b’s is infinite. The parenthetical afterwards is not part of the input or output, but simply represents the end state of how many shirts of each color have not been thrown out.
1 1 1 1 b
(0 b, 1 w left)

1 999 1 999 b
(0 b, 999 w left)

1 999 1 999 w w w w w w w w b
(0 b, 999 w left)

2 999 1 999 b w w w b
(0 b, 999 w left)

2 999 2 999 b w w w b w b w w w b
(0 b, 999 w left)

5 6 3 1 w w w w w b b b b b b b b b b b b b b w
(5 b, 0 w left)

General rules:

This is code-golf, so shortest answer in bytes wins.
Default input rules apply for the first four arguments. For the arbitrarily long input sequence after the first four arguments, input must come from a source which can provide input one character or byte at a time, of theoretically infinite length, such as STDIN or some other stream. If your language of choice does not support these features, see above.

05AB1E, 14 bytes
*Š*‚[ICÓ-W_iõq

Try it online!
Uses p instead of b, because pink is prettier than black.
Explanation:
*             # number of white shirts * durability of white shirts
 Š            # swap
  *           # number of pink shirts * durability of pink shirts
   ‚          # pair the two products in a list
[             # infinite loop
 I            # get the next input
  =           # print (only in the TIO version, not actually needed)
   C          # convert from binary (this yields 51 for p and 58 for w, don't ask why)
    Ó         # list of exponents in the prime factorization
     -        # subtract from the durability list
      W_i     # if the minimum of the list is now 0
         õq   # exit without printing anything

The convert from binary/hexadecimal are sometimes so weird when using characters that aren't part of their conversion.. :S

C(tcc), 117 116 90 bytes
Uses 0x01 instead of b, and 0x02 instead of w.
main(a,b,c,d){scanf("%d%d%d%d",&a,&b,&c,&d);a*=c;for(b*=d;a*b;~c?b-=!c:a--)c=getchar()-2;}

Try it online!

How can I tell it's working from the link? I think you'd need to use some bash for that, but I have no clue how

Thank you, you found a problem. I am currently working on it. I believe you do not need bash for testing this program.

05AB1E, 28 22 bytes
*U*V[XY*_iõq}©XαUY®≠-V

Inputs are separated by newlines in the order amountOfWhiteShirts, whiteDurability, amountOfBlackShirts, blackDurability, shirtDigits..., where white shirts are 1 and black shirts are 0.
-3 bytes thanks to @Grimy by bringing to my attention that an input of 1/0 instead of "w"/"b" is allowed for the shirts.
Try it online or try it online with additional = to print shirts without popping to verify it works as intended.
Explanation:
*          # Multiply the first two (implicit) input-integers
 U         # Pop and store it in variable `X`
*          # Multiply the next two (implicit) input-integers
 V         # Pop and store it in variable `Y`
[          # Start an infinite loop:
 XY*_i  }  #  If either `X` or `Y` is 0:
      õ    #   Push an empty string, since we don't want any (implicit) output
       q   #   And stop the program
 ©         #  Store the next (implicit) input in variable `®` (without popping)
  Xα       #  Get it's absolute difference with `X` (basically `X-input`)
    U      #  Pop and store it as new value for `X`
  ®≠       #  Push `®` falsified (1→0; 0→1)
 Y  -      #  Then subtract it from `Y`: `Y-falsified(®)`
     V     #  And also pop and store it as new value for `Y`

*U*X‚ could be *Š*‚. The challenge allows using arbitrary characters instead of b and w, so 'wQ could be entirely removed (just choose 1 for white and 0 for black).

I’ve updated the problem to reinforce the idea that an infinite loop is impossible.

@Grimy I've removed the second program now that the specs have been improved. Thanks for bringing to my attention that 1/0 is allowed for this shirts, though.

@EdMarty Ok, now it makes a bit more sense. :) I've changed my answer accordingly.

14, using 1 and 2 for white and black: *Š*‚[āIQ-W_iõq (TIO)

@Grimy Oh, very nice approach! Love the ā. It looks nothing like my answer though. Maybe it's better if you post it yourself? Apart from the iõq I see nothing of my answer in yours. ;)

Charcoal, 22 bytes
Ｆ²⊞υ×ＮＮＷ∧⌊υＳ§≔υ℅ι⊖§υ℅ι

Try it online! Link is to verbose version of code. Requires both black shirt values, then both white shirt values, then a stream of b and w characters on separate lines, otherwise behaviour is undefined. On TIO if you don't provide enough shirts you can see Charcoal attempt to prompt for input; if you ran locally then you would be able to continue enter b and w characters until you ran out of shirts at which point the program would exit. Explanation:
Ｆ²⊞υ×ＮＮ

Calculate the lifetimes of each colour of shirt.
Ｗ∧⌊υＳ

Repeat until one colour is exhausted.
§≔υ℅ι⊖§υ℅ι

Reduce the lifetime of a shirt according to the worn colour.

C# .NET, 260 bytes
class P{static void Main(string[]a){int b=int.Parse(a[0]);int g=int.Parse(a[1]);int q=g;int r=int.Parse(a[2]);int t=int.Parse(a[3]);int w=t;while(true){if(System.Console.ReadKey().KeyChar<104)q--;else w--;if(q<1){q=g;b--;}if(w<1){w=t;r--;}if(r<1|b<1)return;}}}

No TIO :( Not the shortest but does the job great!EDIT: NOTE: uses no spaces in input and (g)reen and (r)ed instead of (b)lack and (w)hite

Perl 5, 205 bytes
sub f{@s=map[$_>$_[0]?'w':'b',$_>$_[0]?$_[3]:$_[2],1],1..$_[0]+$_[1];while(join('',map$$_[0],@s)=~/bw|wb/){$c=&{$_[4]};(@w=grep$$_[2]&&$$_[0]eq$c,@s)[0][2]=0;@s=grep$$_[2]||++$$_[2]&&--$$_[1],@s if@w<2}@s}

Try it online!
I assumed that picking the least worn out shirt each day doesn't actually influence the result. Any clean shirt of the chosen color will do. I also think that 2 shirts of durability 5 is the same as 1 shirt of durability 10, so we can simplify this to have just two shirts, one black and one white, but that affects the printed (x b, y w left) result so I didn't go that direction.
Ungolfed:
sub f0 {                                                     #as f() but ungolfed
  my($countb,$countw,$durb,$durw,$get_todays_color)=@_;      #inputs
  my @s=( (map{{col=>'b',dur=>$durb,clean=>1}}1..$countb),   #init @shirts
          (map{{col=>'w',dur=>$durw,clean=>1}}1..$countw) );
  while(join("",sort(map$$_{col},@s))=~/bw/){                #while still both colors available
    my $col = &$get_todays_color;                            #todays color
    my @w = grep $$_{clean} && $$_{col} eq $col, @s;         #wearable shirts
    my $s = (sort{$$b{dur}<=>$$a{dur}}@w)[0];                #pick shirt with most durability left for today
    $$s{clean}=0;                                            #dirty in the evening
    map{$$_{clean}=1;$$_{dur}--}grep!$$_{clean},@s if @w==1; #wash if there was only one this morning
    @s=grep$$_{dur}>0,@s;                                    #toss out worn outs
  }
  @s                                                         #return not worn out
}

Python 2, 54 bytes
b,w,B,W=input()
l=[B*b,W*w]
while all(l):l[input()]-=1

Try it online!


text:
Black and white shirt 2
This is a more complicated version of this puzzle. The premise is the same but a few rules differ in a few key places, making for a more complex problem.
Assume I have some number of black shirts and some number of white shirts, both at least 1. Both colors of shirt have a non-zero durability. All shirts of a given color start with the same durability.
Every day, I pick out a clean shirt to wear, and it becomes dirty. Once I run out of all clean black shirts or all clean white shirts, I wash all my dirty shirts of both colors and start over. Clean shirts do not get washed. Whenever a shirt gets washed, its durability goes down by one. Immediately after washing, if the durability of a shirt reaches 0, it must be thrown out.
When picking which shirt to wear of a particular color, I specify whether I choose a shirt with highest (h) or lowest (l) remaining durability.
Challenge:
Take in an arbitrarily long sequence of four indicators (eg. bh bh bl wl bl wh wl bh...) representing my choice of shirt to wear on that day. Continue execution until either my last black shirt or my last white shirt is thrown out. Once this occurs, stop consuming input and print out the remaining shirts’ durabilities.
Inputs:
Number of black shirts, number of white shirts, durability of black shirts, durability of white shirts, and a sequence of shirt selections of arbitrary length at least long enough for one color of shirt to run out (can be considered infinitely long). The selection can be represented by a character pair (eg. bh, bl, wh, wl), or a single character (eg. b, B, w, W). Your choice, as long as there are four distinct inputs of 1 or 2 characters.
Output:
Status of each remaining shirt, sorted by durability. All shirts will be of one color.
Test cases:
The following test cases represent the amount of input the program should need to process before halting. The input is arbitrarily long otherwise.
1 1 1 1 bh
1

1 3 1 10 wh bh
10 10 9

1 5 2 10 wh wh bh wl wl wl bh
10 10 9 8 8

2 5 1 10 bh wh wh wl bl
10 10 9 9 9

1 5 6 5 wl wh bl wl wh bl wl wh bl wl wl bl wl wl wl bl wl wl wl wl bl
4 3 2 1

1 1 1 10 bl wh wh wh wh wh wh wh wh wh wh wh wh
10
#note the processing would stop occurring after the first bl and everything else should be ignored.

General rules:

This is code-golf, so shortest answer in bytes wins.
Default I/O rules apply

Since no one had suggested it yet in your first challenge, it might be a good idea to start using the Sandbox. There you can perfect a challenge and answer questions from users, making modifications where necessary, before posting them to main. Not that anything is wrong with this challenge, since it seems clear to me now that I understand the first challenge. But it's usually still a good idea to use the Sandbox for any new challenge imho, since you might always overlook some edge-cases or unclarities.

I did post the first challenge in the sandbox, but I got no responses after about a day. How long should I normally need to wait?

Typically you want to wait around 48 hours; I sometimes leave challenges there for a week but that's for more complex ones. You can also advertise sandbox challenges in the site chat here before posting for more final feedback -- usually gets me some feedback.

I usually leave challenges for a week no matter what kind of challenge it is; It is simpler and works for all challenges. Also, even for simple challenges, there might be many problems with them.

I did post feedback on your Sandbox question, but you posted it without addressing it.

Perl 5, 254 bytes
sub f{@s=map[$_>$_[0]||0,$_>$_[0]?$_[3]:$_[2],1],1..$_[0]+$_[1];while(join('',map$$_[0],@s)=~/01|10/){($c,$p)=&{$_[4]};(@w=sort{$p*($$b[1]<=>$$a[1])}grep$$_[2]&&$$_[0]eq$c,@s)[0][2]=0;@s=grep$$_[2]||++$$_[2]&&--$$_[1],@s if@w<2}sort{$b<=>$a}map$$_[1],@s}

Try it online!
Ungolfed but same – with tests:
use Test::More tests=>6;
sub fu {
  my($countb,$countw,$durb,$durw,$get_todays)=@_;            #get 5 inputs
  my @s=( (map{{col=>'b',dur=>$durb,clean=>1}}1..$countb),   #init shirts array
          (map{{col=>'w',dur=>$durw,clean=>1}}1..$countw) );
  while(join('',map$$_{col},@s)=~/bw|wb/){                   #while still both colors left
    my($col,$p) = &$get_todays;                              #todays color and preference
    my @w = grep $$_{clean} && $$_{col} eq $col, @s;         #@w=wearable shirts
    my $shirt = (sort{$p*($$b{dur}<=>$$a{dur})}@w)[0];       #pick todays shirt w/most|least durability left
    $$shirt{clean}=0;                                        #dirty in the evening
    map{$$_{clean}=1;$$_{dur}--}grep!$$_{clean},@s if @w==1; #wash if there was only one this morning
    @s=grep$$_{dur}>0,@s;                                    #toss out worn outs
  }
  return sort{$b<=>$a}map$$_{dur},@s
}
my @test=(
'1 1 1 1 bh',                                                             '1',
'1 3 1 10 wh bh',                                                         '10 10 9',
'1 5 2 10 wh wh bh wl wl wl bh',                                          '10 10 9 8 8',
'2 5 1 10 bh wh wh wl bl',                                                '10 10 9 9 9',
'1 5 6 5 wl wh bl wl wh bl wl wh bl wl wl bl wl wl wl bl wl wl wl wl bl', '4 3 2 1',
'1 1 1 10 bl wh wh wh wh wh wh wh wh wh wh wh wh',                        '10'  #should see sequence not empty
);
while(@test){
  my($countb,$countw,$durb,$durw,@col)=split' ',shift@test;
  my @expected                        =split' ',shift@test;
  my $msg='';
  my $feederu=sub{ (shift(@col)//do{@expected='!',$msg='sequence ran out!'})
                   =~/(.)(.)/;($1,{h=>1,l=>-1}->{$2}) };
  my $feeder=sub{ (shift(@col)//do{@expected='!',$msg='sequence ran out!'})
                   =~/(.)(.)/;($1 eq 'w'||0,{h=>1,l=>-1}->{$2}) };
  my @got=fu($countb,$countw,$durb,$durw,$feederu);
 #my @got=f($countb,$countw,$durb,$durw,$feeder);
  $msg='sequense not empty' if @col;
  is(join(" ",@got),
     join(" ",@expected), $msg);
}