Ah, yes I've seen that before.
Ruby calls 'init' when FS starts, or you load a schematic - but that happens before the 'green' schematic starts, and before the values saved with the file get read in. This means that some input values may not be defined when 'init' runs for the first time - your '@px', '@py' and '@width' variables in this case.
When you edit the code in any way, 'init' gets run again to make sure the code is refreshed - and this time will always run properly because the input values are now all present and correct.
So 'init' should only be used for code that doesn't have to read the input values.
Simplest way to deal with it is this...
Give 'init' a different name, e.g. 'makeData' - so that there's no 'auto-run' at startup. Then call 'makeData' before refreshing the screen to make sure that the values are all defined.
On its own, that works, but re-making the array for every redraw will use up CPU for no reason, redefining the same things over and over.
To stop that from happening, you can put in a test to make sure that your new 'makeData' method only runs once...
This works because of the way that Ruby decides true or false for 'if' and 'unless' statements...
'nil' or 'false' => false
Any other value => true
So, if @buttons has been defined already that counts as true, so 'makeData' won't run - if it isn't defined, it will return 'nil', and 'makeData' gets called.
NB) Something to be careful of !!..
Inside Ruby, the number zero counts as "there is a value, and it's zero" - so zero evaluates as true. Not like 'green ' bools' where zero is false. So if you want zero to count as false, be sure to test for it explicitly.