Hacker News new | past | comments | ask | show | jobs | submit login
Show HN: A Stargate in 140 chars of JavaScript (dwitter.net)
377 points by tomxor on Nov 13, 2020 | hide | past | favorite | 55 comments



    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--;)
    x.fillRect(
        S+96, T+54,
        C(Math.atan2(S=i%97-R,T=i/97-R,r=(S*S+T*T)**.5)*9)*20-r&44&&
        r>36?r<42?T/R:S/R:1,
        r>36?r<R:z(3)/5
    )


I'll try to give a rough overview of what's going on here.

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.


Mind-blowing work! Is there any kind of tools for "reverse engineering" or otherwise unrolling/prettifying dweets so I can study them better? It's really cool but I barely know where to begin.


Thanks!

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.

[edit]

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


Wow, excellent advice, many thanks!

> 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.


That raymarching accident is really interesting!

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.


Interesting I haven't heard of those before, thanks for sharing. You put it so much more succinctly!..

> 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


As someone who's written a lot of impressive dweets -- what do you recommend for someone wanting to start out with this type of coding? Just going to dwitter and trying to decipher other people's dweets as well as writing your own?


Pulling apart dweets is a good way to get started and learn new techniques. Whenever I see a dweet I don't yet understand, I can't resist immediately pulling it to pieces - because I know the ceiling of time required to understand it is very low. Remixing is also a nice concept built into Dwitter that tempts you into golfing other people's code further and creating interesting derivatives - both are good motivators to thoroughly understand all of the techniques packed into a dweet.

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.


A few commenters have pointed out this is not 100% vanilla. I've described how minimal dwitter's shim is, but I'd like to demonstrate it: Paste the following into any browser console and it will run all by itself:

Unpacked = 303 chars (+109):

  document.body.innerHTML='<canvas id=c>';x=c.getContext('2d');t=0;setInterval(_=>{for(X=Y=C=Math.cos,t+=1/60,c.width=192,R=48,i=8064;i--;x.fillRect(X+96,Y+54,C(Math.atan2(X=i%97-R,Y=i/97-R,r=(X*X+Y*Y)**.5)*9)*20-r&44&&r>36?r<42?Y/R:X/R:1,r>36?r<R:z(3)/5))z=n=>n--&&C(Y/2+X*Y/R+t*9)*C(Y/4-z(n)*2)-r/6},16)


Or as a standalone HTML file, 272 bytes (EDIT: was 284 before, I did try a bit more) suffice:

    <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>


Legend


I can shrink it to 50 chars:

window.location='https://www.dwitter.net/d/20584';


I for one think that's funny and clever


dtwitter does some setup beforehand to make this work - creating a canvas, saving a few math functions, etc. Here's a jsfiddle with those set up if you want to play with it.

https://jsfiddle.net/2mt94x5n/


Just curious, is it possible add execution of 140 chars snippets (in JS, Py, etc.) as feature to Nitter?[0]

[0] https://github.com/zedeus/nitter


Thanks!

I tried deobsfucating as much as I could, but I had to go.

https://jsfiddle.net/omgz5xjd/16/

I also missed tomxor's explanation which made things more clear. Initially I thought there was some sort of scanline thing going on.


ops, wrong url, this is the correct one

https://jsfiddle.net/capsadmin/n1ovq5fw/


Nice to see that the spirit of mid-90s demo scene never really died.


Check out ShaderToy if you haven't seen it before: https://www.shadertoy.com/browse


how can a person actually run this? If I paste it into my JS console, I just see Uncaught EvalError: Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of script in the following Content Security Policy directive: "script-src 'self' 'unsafe-inline' https://www.google.com/recaptcha/ https://www.gstatic.com/recaptcha/ https://cdnjs.cloudflare.com/".


Dwitter sets up a minimal shim with a canvas `c` and 2d context `x`, and some shortcuts to math functions sin `S`, cos `C`, tan `T`, and then runs a 60 FPS loop with time `t`.

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.


What @tomxor said! To try it out, go to https://example.com and run the following setup code first:

  c = document.createElement('canvas');
  document.body.appendChild(c);
  x = c.getContext('2d');
  S = Math.sin;
  T = Math.tan;
  C = Math.cos;
  t = 0;
You will then be able to paste the tweet code `eval(unescape(...` into the console and see one frame rendered. :-)


You can also go to data:text/html,<body> instead of example.com (if I got the syntax right for the data URI, I’m on mobile)


Just tried, that works too! Thanks


You can do all sorts of next hacks with the data URI syntax :)


Why does escape'ing and un-escape'ing that string result in code?


There is a critical bit of regex in between. In short this technique unpacks two ASCII characters from each unicode point of the input string. Any ASCII safe code can be packed with this dweet: https://www.dwitter.net/d/11852

The escape function [0] 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.

[0] https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...


I was wondering how (escape`str`) is valid JS to invoke the escape function. Now I know it's called "tagged template literal."


you are a text master. we should call you Master Text.


That's cool. Now make it render the effects of a blackhole on a stargate wormhole ala SG-1 episode: "A Matter of Time".


Done :) https://www.dwitter.net/d/20603

Only need to save result of atan2 `a` (angle) and then tweak the ripple effect in z:

  C(a/3+r+t*9)*C(T/5+z(n)*2)


Haha! You're awesome man!


never heard of this website, really cool! and amazed at what you can do with 140 chars of javascript and some math :D


I love it. And it only uses 61MiB of RAM!


Difficult to tell if sarcasm or not ;)


I was wondering this too :) But I can accept the comment as valid interpreted either way.

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.


And an entire CPU core. I still love it as well though.


This is nice! One thing, I try to copy/paste the code from this dweet and paste into my VSCode instance - it immetiately crashes (macOS). Anyone has the same issue? It only seems to crash when I start copying starting at egal - if I include the function() part it does not crash?


Strange. It's possible the syntax highlighter is getting stuck on the UTF-16 surrogate pairs. Does it happen if you change the language to plain text before pasting in? alternatively try pasting in the decoded version (you can get it by replacing 'eval' with 'throw' in dwitter)... if the later still causes it to hang then It must be attempting to evaluate it!


This is actually really cool, nice work komrade


I don't like JS on web & desktop, but this one[0] is cute.

[0] https://www.dwitter.net/d/7063


Is the disclaimer really necessary or are you making a joke about how people can't even share the smallest of things without telling us they dislike Javascript?


> Is the disclaimer really necessary

Yes.[0]

[0] https://news.ycombinator.com/item?id=16191843


Why? This seems like an irrational fear, it’s not specific to JavaScript in any way. All code is subject to those exploits. Anything you run in any language.


Oh please. Is this suppose to be some sort of gotcha about the dangers of using JavaScript? I don't see how the existence of hardware level (CPU) vulnerability that could impact any software, including the browser itself, applies here.

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.


> but doing it based on some misguided notion of security

What browser you use? I suppose it is Chrome/Chromuim-based browser of Firefox.

Did you read its changelogs? There are a lot of JavaScript-related vulnerability issues fixed this year (as in previous 10 years), right?


For most people the value provided by using the modern web, which for all intents and purposes requires JS, far outweighs the risks.

There are of course exceptions, those in particularly sensitive situations, but in those cases other precautions should be taken as well.

And yes, the text based browser you use may see fewer security patches in the change log, but it is also far less tested. The user base is just not there. If no JavaScript is that important to you it can easily be disabled in Firefox and I'd wager you'd come out ahead in terms of security.


> If no JavaScript is that important to you it can easily be disabled in Firefox

JFTR, Is not Firefox GUI itself uses/based on JS?[0]

[0] https://techjourney.net/disable-javascript-in-firefox/



And let's not forget 100-200 MiB of browser and OS. This is truly amazing in terms of space. /Rant


You are technically correct, but missing the point. It's an artificial constraint in a common context for fun.

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.


do you have a picture or writeup of a crab adder?


I was refering to this article [0] where some computer scientists test out swarm crab computing in Japan, it's a fun read :) you can get the full text using reader mode in firefox.

[0] https://www.technologyreview.com/2012/04/12/186779/computer-...

I wish I had enough time (and crabs) to try this myself.


You could write a compatible renderer in a few KB or less.




Join us for AI Startup School this June 16-17 in San Francisco!

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: