Today’s solution was a nice combination of Dusa and non-Dusa logic.

View the core logic on dusa.rocks%20%3A-%0A%20%20%20%20makeRanges%20(pair%20XA%20Y)%20(pair%20XC%20Y)%2C%20XA%20%3C%20XC%2C%0A%20%20%20%20xRange%20XI%20is%20(range%20XA%20XB)%2C%0A%20%20%20%20yRange%20YI%20is%20(range%20Y%20_).%0AmakeRanges%20(pair%20XA%20Y)%20(pair%20XB%20Y)%20%3A-%0A%20%20%20%20makeRanges%20(pair%20XA%20Y)%20(pair%20XC%20Y)%2C%20XA%20%3E%20XC%2C%0A%20%20%20%20xRange%20_%20is%20(range%20XC%20XB).%0Aafter%20(pair%20(s%20XI)%20YI)%20is%20(pair%20XI%20YI)%20%3A-%0A%20%20%20%20makeRanges%20(pair%20XA%20Y)%20(pair%20XC%20Y)%2C%20XA%20%3E%20XC%2C%0A%20%20%20%20xRange%20XI%20is%20(range%20XC%20XB)%2C%0A%20%20%20%20yRange%20YI%20is%20(range%20Y%20_).%0A%0AmakeRanges%20(pair%20X%20YB)%20(pair%20X%20YC)%20%3A-%0A%20%20%20%20makeRanges%20(pair%20X%20YA)%20(pair%20X%20YC)%2C%20YA%20%3C%20YC%2C%0A%20%20%20%20yRange%20_%20is%20(range%20YA%20YB).%0Aafter%20(pair%20XI%20YI)%20is%20(pair%20XI%20(s%20YI))%20%3A-%0A%20%20%20%20makeRanges%20(pair%20X%20YA)%20(pair%20X%20YC)%2C%20YA%20%3C%20YC%2C%0A%20%20%20%20xRange%20XI%20is%20(range%20X%20_)%2C%0A%20%20%20%20yRange%20YI%20is%20(range%20YA%20YB).%0AmakeRanges%20(pair%20X%20YA)%20(pair%20X%20YB)%20%3A-%0A%20%20%20%20makeRanges%20(pair%20X%20YA)%20(pair%20X%20YC)%2C%20YA%20%3E%20YC%2C%0A%20%20%20%20yRange%20_%20is%20(range%20YC%20YB).%0Aafter%20(pair%20XI%20(s%20YI))%20is%20(pair%20XI%20YI)%20%3A-%0A%20%20%20%20makeRanges%20(pair%20X%20YA)%20(pair%20X%20YC)%2C%20YA%20%3E%20YC%2C%0A%20%20%20%20xRange%20XI%20is%20(range%20X%20_)%2C%0A%20%20%20%20yRange%20YI%20is%20(range%20YC%20YB).%0A%0AonEdge%20X%20Y%20is%20%7B%20false%3F%20%7D%20%3A-%20xRange%20X%20is%20_%2C%20yRange%20Y%20is%20_.%0AonEdge%20X%20Y%20is%20true%20%3A-%20after%20(pair%20X%20Y)%20is%20_.%0A%0Aside%20XL%20YL%20is%20left%20%3A-%0A%20%20%20%20after%20(pair%20XA%20YA)%20is%20(pair%20XB%20YB)%2C%0A%20%20%20%20DX%20%3D%3D%20minus%20XB%20XA%2C%0A%20%20%20%20DY%20%3D%3D%20minus%20YB%20YA%2C%0A%20%20%20%20XL%20%3D%3D%20plus%20XA%20DY%2C%0A%20%20%20%20YL%20%3D%3D%20minus%20YA%20DX%2C%0A%20%20%20%20onEdge%20XL%20YL%20is%20false.%0A%0Aside%20XL%20YL%20is%20left%20%3A-%0A%20%20%20%20after%20(pair%20XA%20YA)%20is%20(pair%20XB%20YB)%2C%0A%20%20%20%20DX%20%3D%3D%20minus%20XB%20XA%2C%0A%20%20%20%20DY%20%3D%3D%20minus%20YB%20YA%2C%0A%20%20%20%20XL%20%3D%3D%20plus%20DX%20(plus%20XA%20DY)%2C%0A%20%20%20%20YL%20%3D%3D%20plus%20DY%20(minus%20YA%20DX)%2C%0A%20%20%20%20onEdge%20XL%20YL%20is%20false.%0A%0Aside%20XR%20YR%20is%20right%20%3A-%0A%20%20%20%20after%20(pair%20XA%20YA)%20is%20(pair%20XB%20YB)%2C%0A%20%20%20%20DX%20%3D%3D%20minus%20XB%20XA%2C%0A%20%20%20%20DY%20%3D%3D%20minus%20YB%20YA%2C%0A%20%20%20%20XR%20%3D%3D%20minus%20XA%20DY%2C%0A%20%20%20%20YR%20%3D%3D%20plus%20YA%20DX%2C%0A%20%20%20%20onEdge%20XR%20YR%20is%20false.%0A%0Aside%20XR%20YR%20is%20right%20%3A-%0A%20%20%20%20after%20(pair%20XA%20YA)%20is%20(pair%20XB%20YB)%2C%0A%20%20%20%20DX%20%3D%3D%20minus%20XB%20XA%2C%0A%20%20%20%20DY%20%3D%3D%20minus%20YB%20YA%2C%0A%20%20%20%20XR%20%3D%3D%20plus%20DX%20(minus%20XA%20DY)%2C%0A%20%20%20%20YR%20%3D%3D%20plus%20DY%20(plus%20YA%20DX)%2C%0A%20%20%20%20onEdge%20XR%20YR%20is%20false.%0A%0Aside%20X2%20Y2%20is%20S%20%3A-%0A%20%20%20%20side%20X%20Y%20is%20S%2C%0A%20%20%20%20deltaFor%20_%20is%20(pair%20DX%20DY)%2C%0A%20%20%20%20X2%20%3D%3D%20plus%20X%20DX%2C%0A%20%20%20%20Y2%20%3D%3D%20plus%20Y%20DY%2C%0A%20%20%20%20onEdge%20X2%20Y2%20is%20false.%0A%20%20%20%0Aoutside%20is%20%7B%20left%2C%20right%20%7D.%0Aoutside%20is%20S%20%3A-%20side%200%20_%20is%20S.%0Aoutside%20is%20S%20%3A-%20side%20xRangeMax%20_%20is%20S.%0Aoutside%20is%20S%20%3A-%20side%20_%200%20is%20S.%0Aoutside%20is%20S%20%3A-%20side%20_%20yRangeMax%20is%20S.%0A%0Ainside%20is%20left%20%3A-%20outside%20is%20right.%0Ainside%20is%20right%20%3A-%20outside%20is%20left.%0A%0AxRange%200%20is%20(range%200%201).%0AxRange%201%20is%20(range%201%202).%0AxRange%202%20is%20(range%202%203).%0AxRange%203%20is%20(range%203%204).%0AxRange%204%20is%20(range%204%205).%0AxRange%205%20is%20(range%205%206).%0AxRange%206%20is%20(range%206%207).%0AxRangeMax%20is%206.%0AyRange%200%20is%20(range%200%201).%0AyRange%201%20is%20(range%201%202).%0AyRange%202%20is%20(range%202%203).%0AyRange%203%20is%20(range%203%205).%0AyRange%204%20is%20(range%205%206).%0AyRange%205%20is%20(range%206%207).%0AyRange%206%20is%20(range%207%208).%0AyRange%207%20is%20(range%208%209).%0AyRange%208%20is%20(range%209%2010).%0AyRangeMax%20is%208.%0A%0Afield%20ref16%200%20is%20%22U%22.%0Afield%20ref16%201%20is%202.%0Afield%20ref15%200%20is%20%22L%22.%0Afield%20ref15%201%20is%202.%0Afield%20ref14%200%20is%20%22U%22.%0Afield%20ref14%201%20is%203.%0Afield%20ref13%200%20is%20%22R%22.%0Afield%20ref13%201%20is%202.%0Afield%20ref12%200%20is%20%22U%22.%0Afield%20ref12%201%20is%202.%0Afield%20ref11%200%20is%20%22L%22.%0Afield%20ref11%201%20is%201.%0Afield%20ref10%200%20is%20%22U%22.%0Afield%20ref10%201%20is%202.%0Afield%20ref9%200%20is%20%22L%22.%0Afield%20ref9%201%20is%205.%0Afield%20ref8%200%20is%20%22D%22.%0Afield%20ref8%201%20is%202.%0Afield%20ref7%200%20is%20%22R%22.%0Afield%20ref7%201%20is%202.%0Afield%20ref6%200%20is%20%22D%22.%0Afield%20ref6%201%20is%202.%0Afield%20ref5%200%20is%20%22L%22.%0Afield%20ref5%201%20is%202.%0Afield%20ref4%200%20is%20%22D%22.%0Afield%20ref4%201%20is%205.%0Afield%20ref3%200%20is%20%22R%22.%0Afield%20ref3%201%20is%206.%0Afield%20ref2%200%20is%20ref3.%0Afield%20ref2%201%20is%20ref4.%0Afield%20ref2%202%20is%20ref5.%0Afield%20ref2%203%20is%20ref6.%0Afield%20ref2%204%20is%20ref7.%0Afield%20ref2%205%20is%20ref8.%0Afield%20ref2%206%20is%20ref9.%0Afield%20ref2%207%20is%20ref10.%0Afield%20ref2%208%20is%20ref11.%0Afield%20ref2%209%20is%20ref12.%0Afield%20ref2%2010%20is%20ref13.%0Afield%20ref2%2011%20is%20ref14.%0Afield%20ref2%2012%20is%20ref15.%0Afield%20ref2%2013%20is%20ref16.%0Afield%20ref1%20%22instructions%22%20is%20ref2.%0A)

View the program on val.town

My core Part 2-specific solution was to collapse ranges in the X and Y directions that didn’t have any corners in them, which effectively. This only affected 1 row in the example for part 1, which collapsed two rows into 1. (I wrote out the height of each row to the right of the row for debugging purposes.)

####### 1
#*****# 1
###***# 1
..#***# 2
###*### 1
#***#.. 1
##**### 1
.#****# 1
.###### 1

But for the sample input for Part 2, the monumentally large grid gets collapsed into something manageable.

#####........ 1
#***#........ 56406
#***#######.. 1
#*********#.. 299945
#*****###*#.. 1
#*****#.#*#.. 143900
###***#.#*#.. 1
..#***#.#*#.. 419392
..#***#.#*### 1
..#***#.#***# 266680
..#####.##### 1

Dusa made it very easy to access the series of “signpost” X and Y coordinates that actually have corners on them, in sorted order, and then I built the ranges in Javascript and shipped that back into Dusa.

The “inside/outside” calculation was a repeat of the Day 10 calculation, done ever-so-slightly-more-robustly this time.

The only unfortunate surprise was that the “flood fill” calculation that fills in all of the inside was causing OOM errors in Dusa, which… that really didn’t seem like it should be the case, I think something is eating memory in a way I don’t understand. So I had to do the flood fill calculation in JavaScript, but given that I’d used Dusa to paint the inside 1-layer deep, it was a straightforward nested loop in JS to complete the flood fill.

Before flood fill
#####........ 1
#***#........ 56406
#*.*#######.. 1
#*..******#.. 299945
#*...*###*#.. 1
#**..*#.#*#.. 143900
###*.*#.#*#.. 1
..#*.*#.#*#.. 419392
..#*.*#.#*### 1
..#***#.#***# 266680
..#####.##### 1
After flood fill
#####........ 1
#***#........ 56406
#***#######.. 1
#*********#.. 299945
#*****###*#.. 1
#*****#.#*#.. 143900
###***#.#*#.. 1
..#***#.#*#.. 419392
..#***#.#*### 1
..#***#.#***# 266680
..#####.##### 1