Maximum Capacity Debugging
From the bottom to the top of the RAM, you have:


the .data section (statically allocated initialized variables)
the .bss section (statically allocated non initialized variables)
the heap (dynamic allocations with malloc() and new)
free space
the stack (return addresses and automatic local-variable allocations)


See the memories of an Arduino for a more lengthy
discussion.

The sizes of the .data and .bss sections are known at build time. This
is the “Global variables” memory usage reported at the end of the
compilation. The rest of the RAM is available for the heap and the
stack. This is the amount reported as “leaving 1852 bytes for local
variables”, where “local variables” is a slight oversimplification.

Your example program doesn't use heap allocation. It does use some
static allocation, mostly for the Serial buffers, and a lot of stack
for:


the return addresses from the calls to main() and setup() (4
bytes)
the local arrays a and si (N+4 bytes)
the memory needed by sprintf() or Serial.print() (unknown)


Even ignoring the last item, you can see that the whole RAM will be
consumed when 4 + N + 4 = 1852, i.e. N = 1844. As you make N larger than
this value, the stack bumps into the .bss section, and you start
corrupting your global variables. The behavior you describe is
consistent with the program corrupting the internal data structures of
the Serial object.


  How should I debug, measure, dimension, or prevent this situation


There is no easy way to determine beforehand the amount of heap and
stack your program will consume. The usual solution is to measure and
report it at run time. See for example the MemoryFree library
(there are many similar to this).