float2 singularitySpiral(float2 c, float time, float iterator)
{
	float radiusSq = max(dot(c, c), 0.0001);
	float angle = 0.5 * log(radiusSq) + time * iterator;
	return float2(
		c.x * cos(angle + 0.0) + c.y * cos(angle + 11.0),
		c.x * cos(angle + 33.0) + c.y * cos(angle + 0.0)) / max(iterator, 0.001);
}

float4 shadeVideo(ShaderContext context)
{
	float2 resolution = max(context.outputResolution, float2(1.0, 1.0));
	float2 fragCoord = context.uv * resolution;
	float safeScale = max(scale, 0.001);
	float safeRingRadius = max(ringRadius, 0.001);
	float safeTightness = max(tightness, 0.001);
	float seed = context.startupRandom;
	float time = context.time * speed + seed * 37.0;

	float2 p = (fragCoord + fragCoord - resolution) / resolution.y / safeScale;
	p -= center + float2(sin(seed * 6.2831853), cos(seed * 6.2831853)) * 0.035;

	// Build a skewed coordinate system around an offset "black hole" so the
	// waves pinch and stretch instead of staying radially symmetric.
	float iterator = 0.2;
	float2 diagonal = normalize(float2(-1.0 + seed * 0.5, 1.0 - seed * 0.35));
	float2 blackholeCenter = p - iterator * diagonal;
	float gravity = iterator * strength / max(dot(blackholeCenter, blackholeCenter), 0.0001);
	float2 skew = diagonal / (0.1 + gravity);

	float2 c = float2(p.x + p.y, p.x * skew.x + p.y * skew.y);
	float2 v = singularitySpiral(c, time, iterator);
	float2 waves = float2(0.0001, 0.0001);

	// Iterative sine feedback creates the accretion texture; the iterator value
	// also damps later steps to keep the pattern stable.
	for (; iterator < 9.0; iterator += 1.0)
	{
		waves += 1.0 + sin(v);
		v += 0.7 * sin(v.yx * iterator + time) / iterator + 0.5;
	}

	float diskRadius = length(sin(v / 0.3) * 0.4 + c * float2(2.0, 4.0));
	float disk = 2.0 + diskRadius * diskRadius * (0.25 * safeTightness) - diskRadius;
	float centerDarkness = 0.5 + 1.0 / max(dot(c, c), 0.0001);
	float rim = 0.025 + abs(length(p) - safeRingRadius) * safeTightness;
	// Exponential falloff turns the accumulated wave field into bright rims and
	// a darker center without hard thresholds.
	float4 redBlueGradient = exp(c.x * float4(0.6, -0.4, -1.0, 0.0) * colorShift);
	float4 waveColor = waves.xyyx;

	float4 color = 1.0 - exp(-redBlueGradient / max(waveColor, float4(0.0001, 0.0001, 0.0001, 0.0001)) / disk / centerDarkness / rim * brightness);
	color.a = 1.0;
	return saturate(lerp(color, context.sourceColor, sourceMix));
}
