Text Plasmosis

by Plex / BionFX

Last edited on 14 Feb 2024 by Quarryman. See all edits

4 comments

BSC - 19:58 12 February 2024 #

This is utterly crazy ... I hope to see the source code one day.

Plex - 18:48 13 February 2024 #

I just posted a lengthy explanation here:

https://www.reddit.com/r/tinycode/comments/1aq13hj/text_plasmosis_14_byte_plasma_winner_of_16b/

Thanks for asking. :-)

wbc - 21:06 13 February 2024 #

this is illegal! :D can't believe it's only 16 bytes!

Plex - 22:08 13 February 2024 #

For those without a reddit account, I copied it overhere:

After getting a request for the source code (and because I m proud of it), I tried to do a short write up of the inner workings of this effect. Let's start with the code:

[org 100h]

les ax,[si]
next16bitPixel:
dec ax
stosw
add ax, [es:di+bx]
rcr ax, 1
dec ax ; not-needed (only added in compo-version to speed up progression)
dec ax ; not-needed (only added in compo-version to speed up progression)
xor bl, 160
jmp next16bitPixel

== How does it work ==

At it's heart this effect is not a plasma, but it is a fire-effect. What those normally do is: They iterativley update video memory by replacing a pixel by the average of its neighbouring pixels and its old value. For the fire to fade out, the new value also gets decremented by a fixed amount every frame until it reaches zero.To preserve the fire burning and not turn completely black, you do add new hotspots of random noise to the image. Those then get smoothed out and slowly fade away by all the averaging and decrementing.

This is what is happening at the heart of "Text plasmosis" as well, but with the following specialties:

The random noise is implicitly added by the fade-out: The `dec ax` does not stop when reaching zero. It wrapps around to the highest value possible. This saves code twice: You do not need to check for zero and you don't need code for adding random noise.

This code does does only do _one_ "averageing"-operation (`add ax, [es:di+bx]` and `rcr ax, 1`) per pixel. This calculates the average of the last pixel value written and one pixel other pixel in video ram. This other pixel is alternating between the next pixel that will be written and the one exactly one line below it. This is achieved by doing `xor bl, 160`, which flips the bx register back and forth between 0 and 160, which is added as an offset to the memory operation `[es:di+bx]`

The effect does calculate the 'fire' using 16bit values. Because of the specialties of the text mode memory layout, where two bytes make up a character on screen (one byte color, the other the ascii code of the character), this makes the upper 8 bit of the fire define the color and the lower 8 bit define the character (which is of minor importance for the visuals). This leads to very smooth outlines of the color patches, because the upper 8bit of the fire do change much slower than the lower ones.

I first discovered this effect in graphics mode (see: https://demozoo.org/productions/337776/) and had the problem, that it was hard to get the fire started. You need enough asymmetries in video ram initially. Often the effect turned out to show the same values for all the pixels that just kept iterating through the palette. The smallest version I could get working in mode13h needed 20 bytes, so I decided to add music and release it as a 32 byte effect. In graphics mode this effect looks a bit different because it has vertical stripes that stem from the alternating high-byte and low-byte of the 16bit pixels the calculation uses.

So when realizing, that that those 16bit values perfectly matched memory layout in text mode, I got lucky trice:

I got rid of the vertical stripes which made better visuals.

I got rid of the code to initialize graphics mode, because you start in text mode by default.

There already were enough asymmetries in text mode memory initially, so I could get one of my smaller version working, that did only 'blinking' in graphics mode.

The first version i had needed 14 bytes, but it did take quite long to come out of initialization phase (where the asymmetries distribute across video memory and start showing the plasma effect in the visible part of it). And because I did not want to make the audience wait for 3 minutes, I used two more bytes to speed up progression. (see code above)

That's all I can think of so far ...

---------------------------------------

p.s.:

Addendum:"... standing on the shoulders of giants."

I deeply need to thank all the others size coders whose work I built upon. For example I never would have figured out myself how to make `les ax,[si]` load 'es' with an address that can be used to write to text video memory.

It's so great to do coding in a area of demo coding, where you can have a look at the code of the amazing creations of others to learn from those, because they are sooo small. Any disassembler will do. :-)

Thanks also to all the maintainers of http://www.sizecoding.org/

... and many thanks to the orga team of https://lovebyte.party/ because they did such a great demo party and because they allowed my entry in even after the deadline and did the extra work of re-doing part of their preparation. (That was necessary, beause I had the idea for text mode during one of the competitions ... so the entry is completely party coded :-) )

p.p.s. I later found an even shorter version (only 13 bytes) of the effect with slightly different visuals using 8bit values:

[org 100h]

les ax,[si]
nextPixel:
dec ax
stosb
add al, [es:di+bx]
rcr al, 1
xor bl, 160
jmp nextPixel