Transmission — MS Hail Mary / Tau Ceti

PROJECT
HAIL MARY

two shaders · two screen savers · one mission

A pair of Metal shaders inspired by Andy Weir’s Project Hail Mary: light lanes streaking through hyperspace, and a bokeh cloud of astrophage racing toward the camera. Packaged as two native macOS .saver bundles.

01
hyperdrive · light-lane parallax
Space Waves
MetalSwiftUI::Layerstitchable4 Palettes

Algorithm SPACEWAVES.METAL

Layered light-lane parallax

The screen is sliced into horizontal lanes. Each lane is an independent object with its own speed, phase and color, then four such grids are stacked at different scales to deliver the “jump to hyperspace” parallax effect.

  1. Rotate UV. Rotate coordinates by angleRadians so lanes can travel at any angle.
  2. Lane split. p.y * laneDensity — integer part is the lane ID, fractional part is position inside the lane.
  3. Per-lane hash. Pseudo-random speed, color and phase via fract(sin(seed·k)).
  4. Pulse core. pow(sin(x), 8.0) produces a narrow, concentrated flash instead of a smeared wave.
  5. Sharp edges. smoothstep(0.45, 0.15, distToCenter) keeps solid fill in the middle with a crisp fall-off.
  6. Palette lookup. Four curated palettes: Hail Mary, Greyscale, Fire Flow, Neon Ocean.
  7. Additive blend. Final color is added on top of the original layer.sample().
grids
4 layers
scale
1 + (i-1)·1.5
pulse
sin⁸
speed
0.5 – 2.0×
CAM 01 · HAIL MARY BRIDGE
ω = 0.40 c
spaceWaves.metal Metal / MSL
Step 1 — slice the screen into lanes. The vertical coordinate is multiplied by lane density; floor yields a unique lane ID that seeds every random property of that lane.
for(int i = 1; i <= grids; i++) {
    float fi    = float(i);
    float scale = 1.0 + (fi - 1.0) * 1.5;
    float laneDensity = layersCount * scale * 0.4;

    float lane      = p.y * laneDensity;
    float laneIndex = floor(lane);
    float laneFract = fract(lane);

    float seed     = laneIndex + fi * 133.33;
    float rndSpeed = fract(sin(seed * 78.233) * 43758.5453);
    float rndColor = fract(sin(seed * 37.719) * 43758.5453);
    float rndPhase = fract(sin(seed * 93.989) * 43758.5453);
spaceWaves.metal Metal / MSL
Step 2 — the glow core. pow(sin)^8 creates a narrow flash capsule while smoothstep over the distance-to-center turns a blurry lane into a crisp laser filament.
float lineSpeed = (rndSpeed * 1.5 + 0.5) * speed;
float moveX    = p.x - (time * lineSpeed) + (rndPhase * 10.0);

float pulse    = pow(sin(moveX * 2.0) * 0.5 + 0.5, 8.0);

float distToCenter = abs(laneFract - 0.5);
float sharpLine    = smoothstep(0.45, 0.15, distToCenter);

float glow = sharpLine * (0.2 + pulse * 1.5);
finalColor += laneColor * glow * intensity * 0.4;
spaceWaves.metal Metal / MSL
Step 3 — the Hail Mary palette. A stepped interpolation through six anchor colors: amber → violet → pink → blue → cyan → orange. Each transition is softened with smoothstep.
static half3 paletteHailMary(float index) {
    half3 c1 = half3(1.0, 0.6, 0.1);
    half3 c2 = half3(0.4, 0.0, 0.9);
    half3 c3 = half3(1.0, 0.1, 0.7);
    half3 c4 = half3(0.1, 0.3, 1.0);
    half3 c5 = half3(0.0, 0.9, 0.9);
    half3 c6 = half3(1.0, 0.4, 0.0);
    if (index < 0.2) return mix(c1, c2, smoothstep(0.0, 0.2, index));
    if (index < 0.4) return mix(c2, c3, smoothstep(0.2, 0.4, index));
    if (index < 0.6) return mix(c3, c4, smoothstep(0.4, 0.6, index));
    if (index < 0.8) return mix(c4, c5, smoothstep(0.6, 0.8, index));
    return mix(c5, c6, smoothstep(0.8, 1.0, index));
}
HailMaryShader.swift Swift / SwiftUI
Step 4 — SwiftUI host. The Metal function spaceWaves is attached via layerEffect with the uniforms wired through: time, speed, intensity, lane density, rotation and palette index.
import SwiftUI

struct HailMaryView: View {
    @State private var start = Date()
    let speed:    Float = 0.4
    let layers:   Float = 12
    let angle:    Float = .pi / 9
    let palette:  Float = 0

    var body: some View {
        TimelineView(.animation) { ctx in
            let t = Float(ctx.date.timeIntervalSince(start))
            Color.black.layerEffect(
                ShaderLibrary.spaceWaves(
                    .boundingRect,
                    .float(t),
                    .float(speed),
                    .float(1.0),
                    .float(layers),
                    .float(angle),
                    .float(palette)
                ),
                maxSampleOffset: .zero
            )
        }
    }
}
02
astrophage swarm · bokeh field
Astrofage
Metal3-Layer DepthBokeh FalloffPerspective
CAM 02 · PETROVA LINE
ρ = 0.92

Algorithm MARY.METAL

3D bokeh particle cloud

Space is partitioned into a grid of cells; each cell carries at most one particle with its own lifetime. Three depth layers produce perspective: far particles are tiny and slow, near particles explode outward from the center as if you were flying into them.

  1. Centered UV. Remap [0,1] into [−0.5, 0.5] with an aspect-ratio correction.
  2. Depth loop. Three phased layers via fract(layerPhase + t·speed) — an infinite depth loop.
  3. Perspective scale. 1 + (1−depth)·drift·3 stretches the grid as particles approach.
  4. Cell hash. A 3×3 neighborhood is scanned; each cell has a 50% chance of hosting a particle with its own seed.
  5. Per-particle life. Individual speed, size (pow(h, sizePow)) and phase offset.
  6. Bokeh falloff. smoothstep(r, r·softness, d) gives a soft disk instead of a hard one.
  7. Color warming. Closer particles shift toward white via mix(color, white, t²).
layers
3 depths
neighbors
3×3 cells
size
0.006 → 0.08
softness
0.35
mary.metal Metal / MSL
Step 1 — stable 2D hash. A deterministic noise function delivers pseudo-random positions, sizes and speeds without any texture asset.
static float hash(float2 p) {
    float3 p3  = fract(float3(p.xyx) * 0.1031);
    p3 += dot(p3, p3.yzx + 33.33);
    return fract((p3.x + p3.y) * p3.z);
}

constant float SIZE_START      = 0.006;
constant float SIZE_END        = 0.08;
constant float SOFTNESS        = 0.35;
constant float BRIGHTNESS_MIN  = 0.005;
constant float BRIGHTNESS_MAX  = 3.0;
mary.metal Metal / MSL
Step 2 — perspective & depth fade. Each layer is phased independently; the closer the particle, the more the grid stretches — a fake forward-flight effect.
float fl   = float(layer);
float seed = fl * 17.31;

float grid = gridBase * (0.85 + fl * 0.06);
float2 layerOff = float2(hash(float2(fl,1.0)),
                          hash(float2(fl,2.0))) * 100.0;

float layerPhase = fl / float(LAYERS);
float depth      = fract(layerPhase + time * speed);
depth = max(depth, 0.05);

float perspScale = 1.0 + (1.0 - depth) * drift * 3.0;
float2 st = centered * perspScale * grid + layerOff;

float layerFade = smoothstep(0.0, 0.4, depth)
                  * smoothstep(1.0, 0.55, depth);
mary.metal Metal / MSL
Step 3 — the bokeh particle. Within the 3×3 neighborhood each cell may host one bright point. Radius grows with t², edges dissolve via smoothstep, color warms toward white near end-of-life.
for (int ox = -1; ox <= 1; ox++) {
  for (int oy = -1; oy <= 1; oy++) {
    float2 cid = cellId + float2(float(ox), float(oy));
    if (hash(cid + seed) > 0.5) continue;

    float h_anim = hash(cid + seed + 30.0);
    float spd    = speedLo + fract(h_anim * 7.13) * (speedHi - speedLo);
    float t      = fract(h_anim + time * spd);

    float sv     = pow(hash(cid + seed + 40.0), sizePow);
    float r      = mix(SIZE_START, SIZE_END * (0.05 + sv*0.95), t*t)
                   * perspScale * grid;

    float d      = length(f - float2(ox,oy) - pp * 0.7);
    if (d > r) continue;

    float bokeh = smoothstep(r, r * SOFTNESS, d);
    float fade  = smoothstep(0.0,0.35,t) * smoothstep(1.0,0.6,t) * layerFade;
    totalGlow += pColor * bokeh * fade * bright;
  }
}
AstrofageShader.swift Swift / SwiftUI
Step 4 — SwiftUI wiring. The shader runs as a colorEffect, so the output is the final pixel color. Time comes from TimelineView, tunables come from @AppStorage.
import SwiftUI

struct AstrofageView: View {
    @AppStorage("density") var density: Double = 160
    @AppStorage("sizePow") var sizePow: Double = 2.4
    @AppStorage("speed")   var speed:   Double = 0.18
    @AppStorage("drift")   var drift:   Double = 0.6

    var body: some View {
        TimelineView(.animation) { ctx in
            GeometryReader { g in
                Color(red: 0.02, green: 0.04, blue: 0.09)
                    .colorEffect(
                        ShaderLibrary.flyingParticles(
                            .float(Float(ctx.date.timeIntervalSinceReferenceDate)),
                            .float2(Float(g.size.width),
                                    Float(g.size.height)),
                            .float(Float(density)),
                            .float(Float(sizePow)),
                            .float(Float(speed)),
                            .float(Float(drift))
                        )
                    )
                    .ignoresSafeArea()
            }
        }
    }
}

Install on macOS

Sonoma / Sequoia · macOS 14+ recommended · unsigned bundle
STEP 01

Download & unzip

Download the .saver.zip you want, then double-click the archive in Finder to extract the .saver bundle.

STEP 02

Open the bundle

Double-click the extracted .saver file. macOS will prompt you to install it either for your user or for all users.

STEP 03

Approve in Privacy & Security

The bundle is unsigned, so macOS will block it. Open System Settings Privacy & Security and click Open Anyway next to the saver’s name.

STEP 04

Select in Screen Saver

Go to System Settings Screen Saver, scroll to Other, and pick HailMary or Astrofage.

STEP 05

Preview & tune

Hit Preview to see the shader full-screen. Use the saver’s Options panel to change speed, density, palette and drift.

Why macOS asks for permission

Both .saver bundles are unsigned (no Developer ID / notarization), because they’re distributed as open shader samples. Gatekeeper flags them on first launch — this is expected. If the “Open Anyway” button doesn’t appear, place the saver manually at ~/Library/Screen Savers/ and run xattr -dr com.apple.quarantine ~/Library/Screen Savers/HailMaryScreenSaver.saver in Terminal. Screen savers on macOS never get access to your files, microphone or camera — they only draw pixels.