@[email protected] @[email protected] After having run it, I now also realize this modified linear shift register makes for an interestingly biased PRNG.
The dynamics is as follows:
base[d] = (base[d - 1] + base[d - 0x29] + base[d]) >> 2 - 1;This adds up three values modulo 65536, but divides by four, then subtracts one. So the output value is always in the range from -1 (FFFF) to 3FFE (16382) - and if only such values are used as input (i.e. if one loop already ran), it'll never wrap (provided we interpret FFFF as -1).
This means that you can almost never get the blink attribute. Which is good, as the blink attribute sucks. Also, the background color can basically never have a red component, which gives it a blue/green-ish tint. With the exception of FFFF. Which is generated when all of base[d-1], base[d-0x29] and base[0] all were zero. But as the character code FF on codepage 437 is a non-breaking space, it won't actually blink, but just be a grey box.
So once this thing is stable, the values tend to decrease. Until they reach 0. Once three zeros are the input of this formula, it'll generate a FFFF (a grey square). If one or more FFFFs are the input of this formula, and the rest is zeroes, it'll always generate 3FFE (a white small square inside a cyan full square).
But as not everything goes to 0 at the same time, some leftover small values tend to be there when the wrap to FFFF happens. This makes it a pretty weird PRNG.
Which brings me to the significance of the 0x29 (which was in the asm code doubled, i.e. a 0x52).
0x29 is 41. I.e. one more than the screen width.
So some of its effect creates a frequency component of slightly more than half a screen as its "wavelength". Which explains why there's usually 2 copies of each detail on the screen, and also why stuff tends to be angled from top left to bottom right.
As for why it tends to move slowly to the left - that I cannot quite tell. It must be an interaction of this shift register with period 32768 of the memory access, however, I do not quite see why.
BTW, there is one interesting failure case of this demo: when the memory area from A000 to AFFF isn't mapped. Then the wraparound will not function anymore. This is lost in my decompilation - the actual code will actually transmit the previous value around in the AX register even through a long loop of open bus (all reads return FFFF) from A0000 to AFFFF. So it will be different on e.g. a 512k PC without EGA, but still work. Verified in 86box. It is more random and chaotic than on a "full" PC with the full 1 MiB of RAM, but it still works on less.