g | x | w | all
Bytes Lang Time Link
188Python + matplotlib250513T084737ZSevC_10
161Pug/HTML250511T181302Zccprog
nanDesmos250508T193305ZCursorCo

Python + matplotlib, 192 188 bytes

Edit:

from math import*
from matplotlib.pyplot import*
R=450
for x in range(-R,R+1):
 if x:y=sqrt(R*R-x*x);t=tan(2*atan(y/x)-3*pi/2);plot((x-y/t,x+(R-y)/t),(0,R),'b',lw=.1)
xlim(-R-9,R+9)
show()

(Can't) Attempt This Online!

Example graphical output: Graphical output


Explanation

Pug/HTML 162 161 bytes

- k=999,i=0,p=" ",M=Math
svg(viewBox=-k+p+-k+p+2*k+p+2*k): while i<k
 line(x1=M.sin(a=M.PI/k*i++)*k y1=M.cos(a)*k x2=M.sin(b=3*a)*k y2=M.cos(b)*k stroke="#0003")

Try it online!

The compiled code is not a valid standalone SVG file, but needs to be rendered by a HTML browser. The result is scalable and has no fixed size, it rather depends on the available viewport.

a caustic with black lines on white background


For the price of 22 bytes of CSS, you can get a rendering with light on black: Codepen. I'm adding it only because I think it looks awesome.

svg{background:#000}

...and for 14 bytes more, you get a round area:

svg{background:#000;border-radius:50%}

Desmos, 43 + 3 = 46 bytes

t=[-4^4...4^4]
y\sqrt{4^8-tt}=x8^5/t-xt+8^5

Try it online!

This is a ray based approach that uses 512 rays to approximate the caustic.

Demonstration of the code

Explanation

Not too much to explain here, t is a list defined to be the range from -256 to 256 inclusive. And our second expression is just a golfed equation of the relevant reflected ray on a circle of radius 256 that intersects the circle at x=t. Because of the golfing done, when t=0 the resulting vertical line doesn't render (since a divide by zero occurs) but desmos handles this gracefully and the effect on the resulting image is not particularly bad. This becomes unnoticeable at higher ray counts.

Scoring

For those unaware, desmos scoring is based on the byte count of text that can be pasted into the expression box as discussed here. This is sufficient for most desmos answers and in general is a good and rigorous way of having "byte counts" for desmos graphs. But as discussed here, this neglects many of desmos' configurable graphical features such as setting the colors of expressions, or importantly for this challenge, setting line opacity. Thus with the current generally accepted rules desmos is unable to complete this challenge as the lines are fully opaque by default leaving only 2 colors in the rendered image.

That being said, I have been given permission by mousetail to make up my own rules for scoring desmos for this challenge! Thus I propose the following: For necessary setting changes the byte count to be added should be the minimum number of bits needed to set the configuration, rounded up to the nearest byte, plus one, for each setting configured. The plus one is a sort of "boundary byte", we can imagine a "meta desmos language" that consists of the code to pasted in followed by a list of settings information, where each chunk of settings information has a special leading byte to identify what setting it's for. In my case here, my settings information is setting the line opacity to .1 in the first (and only) expression. Thus I need 2+1=3 extra bytes to encode this information.

There is some more discussion to be had about desmos byte counts, and this slippery slope is likely the reason why a scheme like mine above has not been adopted. For the purposes of this challenge I'm considering the configuration of the graph's viewing window to not be a part of the byte count, but here there are some open questions. Should the grid lines and axes be counted as a part of the rendered image or are they simply a background artifact? Certainly they can be turned off, and under my scheme above would cost 2 bytes each to do so, meaning another +6 for this answer. But they also aren't even static with the viewing window and really feel more like a HUD than a part of the rendering. This brings us to a possibly even more pressing question. Is the viewing window's position/zoom part of the rendering? Again I haven't counted it here, for this answer all you need to do is zoom out to see the whole image. However, there are a couple of compelling reasons that if it's not in the correct position by default it should be counted. For one, desmos' veiwing window allows the aspect ratio to be changed arbitrarily, even in this answer I could save a byte or two by squishing the y-axis down allowing my expression to draw the caustic in an ellipse rather than a circle. The other problem is that if there's no penalty for windows movement one can essentially beat any graphical challenge just by going to the right place in Tupper's formula. But there are problems with counting it too. Namely, depending on the the aspect ratio of your screen, putting in the same data into the axis limits yields different views. Thus I think it makes sense to not include these viewing conditions in the byte count, and just have reasonable expectations (including no custom aspect ratios) about the amount of window movement needed to see the rendered image.