z = n => n--&&C(T/2+S*T/R+t*9)*C(T/4-z(n)*2)-r/6
for (c.width=192,R=48,i=8064; i--;)
The function z produces the puddle (ahem - "event horizon") through a technique I accidentally found from "orthogonal" raymarching. The accident being the value is supposed to be the distance of an incrementing ray for an SDF (signed distance function), but instead the distance is completely replaced on each recursion resulting in the reflected fluid appearance. I think this is due to the ray distance oscillating backwards and forwards in a way that is similar enough to neighboring rays to not appear noisy while giving the appearance of reflection. The exact functions used for the SDF are not so important so long as it incorporates itself.
fillRect is doing pretty much everything else, by painting each pixel. To achieve shading without fillStyle, the width and height arguments are varied between 0 and 1 to give grey scale shading due to subpixel anti-aliasing. Speculative edges are also achieved with negative values (0 to -1) which creates white gaps and fill adjacent pixels instead.
The chevrons are sampled from a radial fractal a*20-r&44 where 'a' is the angle of the current position wrapped in some periodic function (cosine), and 'r' is the radius. The bitwise AND operator is the key in generating these types of patterns (although OR and XOR also work very well producing other types of patterns), the constants are found through - a lot - of trial and error. In this case the result of the fractal is used as a "truthy" condition (i.e non zero) to toggle between the shaded sides (X/48 or Y/48) and 1 (the dark bits).
Some other hacky details: S and T are the X and Y pixel positions, these names are used so that they can be referenced before they have been assigned because these names have already been assigned unused functions by dwitter. This saves some characters by allowing me to define X and Y inside of atan2 while also passing them as arguments, at the cost of the fillRect X Y position being invalid for the first pixel and from the previous iteration on the rest.
I'm finding a recurring property of dweets that end up packing a lot in - is sharing significant portions of code for different purposes, in this case it's fairly straight forward but still a big win in terms of characters: S, T and r are only defined once and used for both the puddle and the ring. This type of size optimization always gives a lot more road than syntactic wrangling, which while important are only finishing touches. However they cannot be forced, ideas with enough shared code can only be found.
Feel free to ask me anything else.
I've never found prettifiers to help much. Often there isn't much in the way of indentation or newlines for a formatter to be able to objectively insert. I mainly rely on the following while both making my own dweets and unpacking other dweets (which can be just as much fun btw):
1. Use a text editor: Dweets can get very dense and non linear, syntax highlighting can help a lot when deconstructing a dweet (essentially manually prettifying). I also find variable highlighting invaluable for understanding a dweet (i.e where you select one and all of the others are clearly highlighted). Often the code will be arranged in confusing ways just to squeeze extra characters out, e.g putting things in the incrementor position of a for loop 'for(i=8;i--;x.fillRect(blah))', often you will want to rearrange it to make it more legible while you are understanding the code.
2. Use Dwitter: When making new dweets or figuring out the behavior of existing dweets. The code runs as soon as you lift each finger, this creates a feedback loop between your brain and the computer which is faster than any other interactive computing environment i've seen. It seems absurdly simple but it's very powerful and you will learn a lot. JS things.. all of those niggling assumptions that you don't have time or inclination to investigate when writing "proper" code will suddenly be easy and necessary and interesting to figure out. Math things.. I usually suck at math, but when you can play with the variables and functions and watch patterns instantly change on the screen math suddenly feels like the most intuitive thing in the world.
3. Your brain: The lovely thing about code this size is it can all fit in your head with ease... After marinading in your head for long enough eventually the code falls away and the concepts remain, at which point you can think about it, run it, and manipulate it with ease.
Quite a number of dweets are packed using the unicode escape trick these days to get 194 effective characters (including this one). To quickly get the unpacked code just replace 'eval' with 'throw' and it will be printed bellow the dweet.
Finally there is a small but active discord group where you might find people willing to help with golfing (or ungolfing) and figuring things out etc: https://discord.gg/emHe6cP
> feedback loop between your brain and the computer which is faster than any other interactive computing environment i've seen
This is such a critical piece of any workflow. It's often overlooked.
It reminds me of Newton fractals, which basically compute a color for each pixel that depends on the sequence of points visited by the newton root finding algorithm.
This is basically the same thing: tracing the orbit of a function that's very chaotic but has enough local coherence to get a smooth image.
> tracing the orbit of a function that's very chaotic but has enough local coherence to get a smooth image
This is the effect on it's own where I originally discovered it if anyone wanted to play: https://www.dwitter.net/d/15952
Once you get a bit more comfortable I would resist the urge to immediately publish, you will be surprised just how much work you can put into 140 characters. Don't publish until you feel like you can't remove another single character and have the best possible combination... then sleep on it, you will be surprised at the sudden revelations that happen and give you more road to play with... this will happen so long as you are not overly precious about anything - you can't have everything - making frequent sacrifices is what leads you to more interesting combinations.
After building up a stash of unfinished ideas, you will also sometimes notice some synergy between them and can combine them into something more impressive. In the same vein... making tiny code is as much about exploration as experimentation... I discovered this combination that looks like a Stargate - i.e the skill is in finding things, rather than forcing your will upon a canvas.
Unpacked = 303 chars (+109):
<body onload="t=setInterval('with(Math)for(t+=.144,c.width=192,R=48,i=8064;i--;c.getContext`2d`.fillRect(X+96,Y+54,cos(atan2(X,Y)*9)*20-r&44&&r>36?r<42?Y/R:X/R:1,r>36?r<R:z(3)/5))r=hypot(X=i%97-R,Y=i/97-R),z=n=>n--&&cos(Y/2+X*Y/R+t)*cos(Y/4-z(n)*2)-r/6',16)"><canvas id=c>
I tried deobsfucating as much as I could, but I had to go.
I also missed tomxor's explanation which made things more clear. Initially I thought there was some sort of scanline thing going on.
The code is run in this loop with those variables in non-strict mode to allow various hacky things e.g `with()` that are invaluable for code golfing.
The eval escape unescape trick extracts two chars in the ASCII space from each UTF16 character. Here's the encoder: https://www.dwitter.net/d/11852
You can see the content by removing the eval and just outputting the string... alternatively replace 'eval' with 'throw' on dwitter and it will print the source bellow the dweet.
c = document.createElement('canvas');
x = c.getContext('2d');
S = Math.sin;
T = Math.tan;
C = Math.cos;
t = 0;
The escape function  converts unicode values > 0xFF into the string format '%uxxxx`, additionally it will produce two sequences if the unicode point is high enough to require two UTF-16 surrogate pairs. e.g escape() ing the first character '𩡯' results in '%uD866%uDC6F'. The regex throws away the 'uD8' parts of each sequence as we aren't exploiting the full capacity of the unicode space, just the lower 255 values of each surrogate pair. which results in '%66%6F'. Finally this is unescaped which converts these into the separate ASCII chars that were originally packed with dweet 11852 (don't ask me how it packs it I haven't looked yet.)
In all this means it's possible to fit 194 evaluable ASCII chars into a dweet (taking into consideration overhead of the unpacking code). There are also more esoteric methods of fitting more characters or data using unicode that are not generally evaluable, e.g image data. All of these techniques are using more than 140 bytes, but dwitter has a character limit not byte limit. But nothing is free, you must fit any decoders into the same dweet so there is always a balance.
Only need to save result of atan2 `a` (angle) and then tweak the ripple effect in z:
On the one hand 61MB feels like a lot for such a tiny piece of source code, but then we know there is a massive heap of runtime it's relying on in the browser, JS engine, canvas implementation etc etc. Then again, an average "modern" website using 61MB isn't considered all that much for one page! Some of that is surely the rest of Dwitter itself, but probably not a huge portion.
If you refuse to use a modern web browser, that's a totally valid choice, but doing it based on some misguided notion of security is nonsensical.
What browser you use? I suppose it is Chrome/Chromuim-based browser of Firefox.
There are of course exceptions, those in particularly sensitive situations, but in those cases other precautions should be taken as well.
JFTR, Is not Firefox GUI itself uses/based on JS?
The other problem with this argument is that it never ends: If you write on "bare metal" one can argue you are relying on thousands if lines of firmware and microcode. Then you go even lower and one can argue that your high level CISC ISA is doing too much work for you. Eventually you end up with transistors and then people will end up arguing which transistor is the least complex design to fabricate. I will admit trying to compute with sticks, stones and crabs (for real) is a fun idea, but it's hard to achieve much beyond a ripple adder.
I wish I had enough time (and crabs) to try this myself.