Never say something is optimal until there is proof... As limited as the Hack ISA is, it is often surprising what you can squeeze out of it to optimize your code. So I was able to further improve the code on fill.asm. This time I'm really really sure, that the graph shows the optimal pareto frontier - as far as I can tell ;-) If anyone can show me code that is breaking the frontier (faster at same size or smaller at same speed), I will invite you for a virtual paypal drink!
The smallest version with 16 instructions is now faster. The code has now 2 instructions of initialization, but the loop is only 14 instructions long (with 1 instruction skipped in the white case). The initialization is not even required to pass the FillAutomatic test as long as cell [8191] doesn't point to an illegal memory address. So technically you can fullfill the test with even 14 instructions, however that would be cheating :-)
The structure is now:
2 instructions to init [8191]
(loop)
3 instructions to write the color to the screen pointer address (that's why the init is required)
6 instructions update the screen pointer (increased by 1 and kept inside the range 16384-24575),
5 instructions to read the keyboard and load the D register with the color and jump back to loop
The version drawing a full frame is now only 20 instructions long. Looking at it now, the code is so simple that I'm wondering why I didn't do it like that right from the start...
It has 2 separate blocks for the black and white case and the keyboard poll just in between the blocks, either falling through or looping back to the first block. The structure is:
(white)
2 instructions init loop counter
5 instructions loop through 16384-24575 and write 0
(poll)
4 instructions poll keyboard and conditionally jump to white
// black fall-through
2 instructions init loop counter
5 instructions loop through 16384-24575 and write -1
2 instructions jump to poll
Pretty simple, the real challenge is how to make the loop with just 5 instructions...
The graph now distinguishes clearly between the versions polling the keyboard after every cell vs once per frame.