Hm, so fixing all the glitches with transparent tiles ended up needing to burn two vertices:
		s_layer_groups[i] = new_group(get_default_shader());
		layer_shape = new_shape(SHAPE_TRIANGLE_STRIP, get_atlas_image(tile_atlas));
		for (y = 0; y < s_map->height; ++y) for (x = 0; x < s_map->width; ++x) {
			tile = s_map->layers[i].tilemap[x + y * s_map->layers[i].width];
			uv = get_atlas_uv(tile_atlas, tile.tile_index);
			x1 = x * tile_width; x2 = x1 + tile_width;
			y1 = y * tile_height; y2 = y1 + tile_height;
			add_shape_vertex(layer_shape, vertex(x1, y1, uv.x1, uv.y1, rgba(255, 255, 255, 255)));
			add_shape_vertex(layer_shape, vertex(x2, y1, uv.x2, uv.y1, rgba(255, 255, 255, 255)));
			add_shape_vertex(layer_shape, vertex(x1, y2, uv.x1, uv.y2, rgba(255, 255, 255, 255)));
			add_shape_vertex(layer_shape, vertex(x2, y2, uv.x2, uv.y2, rgba(255, 255, 255, 255)));
			if (x < s_map->layers[i].width - 1) {
				tile = s_map->layers[i].tilemap[(x + 1) + y * s_map->layers[i].width];
				uv = get_atlas_uv(tile_atlas, tile.tile_index);
				add_shape_vertex(layer_shape, vertex(x2, y2, uv.x1, uv.y1, rgba(255, 255, 255, 255)));
				add_shape_vertex(layer_shape, vertex(x2, y1, uv.x1, uv.y1, rgba(255, 255, 255, 255)));
			}
		}
		upload_shape(layer_shape);
		add_group_shape(s_layer_groups[i], layer_shape);
		free_shape(layer_shape);
At this point I may as well just use a list instead of a strip, I think.