Batch 3 block idents: Paul's 5% notes (softer Sunday glow, richer tartan, Spooky contrast, subtler tracking line, Movie Night as a feature presentation)
pauljd/davidson-ci: Davidson CI / quality (push) Failing after 2m21s
pauljd/davidson-ci: Davidson CI / ai-review (push) Failing after 1m31s

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-11-03 19:30:20 +00:00
co-authored by Claude Opus 5.5
parent aff469fca3
commit c7bd97d6bc
32 changed files with 90 additions and 29 deletions
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

After

Width:  |  Height:  |  Size: 1.6 MiB

+3 -1
View File
@@ -12,6 +12,8 @@ Each block folder has `review/<block>_contact_sheet.png`, `_final_still.png`, `_
|---|---|---|---|
| Sunday Gold | `orbit-gold-blocks/sunday-gold` | 173 KB | A ribbon of gold light flies in from the planet and writes *Sunday* (URW Z003 calligraphic italic) under the logo; two warm chimes. |
| Scottish Comedy Night | `orbit-three-blocks/scottish-comedy-night` | 172 KB | The three hoops' moons line up; an original Orbit Three tartan (pink, yellow, cyan on purple, 2/2 twill, not any real sett) unrolls; THREE-style tiles drop in, the Y lands squint and the N bumps it straight. Sting: a little reed-like pentatonic run and a comic pop, no bagpipes. |
| Vortex Movie Night | `vortex-blocks/movie-night` | 494 KB | The tunnel slows and dims, letterbox bars slide in with sprocket strips that slow to a stop, chrome MOVIE NIGHT with one glint. Larger than the others because the tunnel is so detailed, but under Vortex's own 1.27 MB ident. Sting: falling whoosh, low boom. |
| Vortex Movie Night | `vortex-blocks/movie-night` | 458 KB | The tunnel slows, letterbox bars slide in with sprocket strips that slow to a stop, the house lights go down (the tunnel dims into a vignette), the logo sits centred, TONIGHT'S FEATURE PRESENTATION between fine rules, then a large chrome MOVIE NIGHT with one glint. Larger than the others because the tunnel is so detailed, but under Vortex's own 1.27 MB ident. Sting: falling whoosh, low boom. |
| Retro Action | `orbit-2-blocks/retro-action` | 164 KB | A glowing road under Orbit 2's planet, headlights streaking past (light only), chrome RETRO ACTION with a star glint and one VHS tracking line. No scanner light, vehicles or show lettering. Sting: synth-bass stab and gated drum. Plays only when entering the booked block. |
| Friday Spooky Teatime | `orbit-2-blocks/spooky-teatime` | 152 KB | A teacup drifts in, its steam becomes a small friendly ghost that says boo, three bats fly round the planet and perch on the wobbly title. Sting: teaspoon clink and a soft, silly wooo. |
**Paul's 5% notes, 3 November (2D only, no 3D re-render):** Sunday's glow softer and quieter; the tartan given woven grain, a light top fold, shade beneath and a soft drop shadow; SPOOKY TEATIME's outline heavier with a soft dark shadow per letter, and the bats a thin lilac rim; Retro Action's tracking line thinner, gentler and dimmer; Movie Night recomposed as a feature presentation (above).
+73 -14
View File
@@ -131,7 +131,7 @@ class Block:
face = ImageFont.truetype(SCRIPT, 132)
ink = joined('Sunday', face, WHITE + (255,))
word = gradient_fill(ink.getchannel('A'), [(0, (255, 236, 176)), (.5, (240, 192, 80)), (1, (176, 120, 32))])
halo = glow(word, 6, 1.1)
halo = glow(word, 5, .55) # Paul's 5% note: keep the glow soft and understated
halo.alpha_composite(word)
word = halo.rotate(5, Image.BICUBIC, expand=True)
self.cache['sunday'] = word
@@ -185,7 +185,10 @@ class Block:
cut = band.copy(); cut.putalpha(ImageChops.multiply(band.getchannel('A'), mask))
if u < 1: # the roll still to unfurl, as a darker rounded edge
ImageDraw.Draw(cut).rounded_rectangle((edge - 16, -2, edge + 4, band.height + 2), 9, fill=(40, 12, 54, 255))
place(canvas, cut.rotate(-2, Image.BICUBIC, expand=True), 50, 480)
turned = cut.rotate(-2, Image.BICUBIC, expand=True)
shadow = Image.new('RGBA', turned.size, (0, 0, 0, 0)); shadow.putalpha(turned.getchannel('A').point(lambda v: v * 150 // 255))
place(canvas, shadow.filter(ImageFilter.GaussianBlur(6)), 54, 488)
place(canvas, turned, 50, 480)
tiles = self.cache.get('tiles') or self.cache.setdefault('tiles', tile_row('SCOTTISH COMEDY NIGHT'))
squint = next(i for i, (c, *_) in enumerate(tiles) if c == 'Y')
for i, (c, tile, x, base_rot) in enumerate(tiles):
@@ -219,13 +222,36 @@ class Block:
for k in range(-1, 42):
x = k * 34 - travel % 34
d.rounded_rectangle((x, y - 7, x + 18, y + 7), 3, fill=(26, 38, 56, 255))
self.channel_logo(canvas, t, 1.2, 640, (54, 98))
# Paul's 5% note, more of a feature presentation: the house lights go down (the tunnel dims into a vignette
# inside the frame), the logo sits centred like a studio card, and a presentation line introduces the title.
dim = kf(t, [(1.2, 0), (1.9, 1)], ease_io)
if dim > 0:
vignette = self.cache.get('vignette')
if vignette is None:
vignette = Image.new('L', (W, H), 0)
ImageDraw.Draw(vignette).ellipse((-160, 40, W + 160, H - 40), fill=255)
vignette = ImageChops.invert(vignette.filter(ImageFilter.GaussianBlur(120)))
vignette = vignette.point(lambda v: 90 + v * 165 // 255)
self.cache['vignette'] = vignette
shade = Image.new('RGBA', (W, H), (0, 0, 0, 0)); shade.putalpha(vignette.point(lambda v: round(v * dim * .78)))
inside = canvas.crop((0, bar, W, H - bar)); inside.alpha_composite(shade.crop((0, bar, W, H - bar))); canvas.paste(inside, (0, bar))
logo = self.logo_at(500)
place(canvas, logo, (W - logo.width) / 2 - 20, 112, kf(t, [(1.2, 0), (1.7, 1)], ease_io))
line = self.cache.get('feature') or self.cache.setdefault('feature', text_layer("TONIGHT'S FEATURE PRESENTATION", font('barlow-regular.ttf', 24), (226, 236, 250, 255), 9))
la = kf(t, [(1.6, 0), (2.0, 1)], ease_io)
place(canvas, line, W / 2, 388, la, 'mt')
reach = kf(t, [(1.7, 0), (2.2, 1)], ease_io) * 150
if reach > 0:
d = ImageDraw.Draw(canvas)
for side in (-1, 1):
x = W / 2 + side * (line.width / 2 + 22)
d.line((x, 401, x + side * reach, 401), fill=(55, 223, 255, 200), width=2)
title = self.cache.get('movie_title')
if title is None:
title = chrome('MOVIE NIGHT', font('anton-regular.ttf', 96), 6, [(0, (255, 255, 255)), (.42, (205, 220, 242)), (.5, (70, 92, 132)), (.56, (36, 52, 88)), (1, (196, 214, 240))])
title = chrome('MOVIE NIGHT', font('anton-regular.ttf', 112), 8, [(0, (255, 255, 255)), (.42, (205, 220, 242)), (.5, (70, 92, 132)), (.56, (36, 52, 88)), (1, (196, 214, 240))])
self.cache['movie_title'] = title
a = kf(t, [(1.75, 0), (2.15, 1)], ease_io)
place(canvas, glint(title, kf(t, [(2.45, 0), (2.9, 1)], ease_io)), 292, 400 + kf(t, [(1.75, 18), (2.15, 0)], ease_out), a)
a = kf(t, [(1.85, 0), (2.25, 1)], ease_io)
place(canvas, glint(title, kf(t, [(2.5, 0), (2.95, 1)], ease_io)), (W - title.width) / 2, 430 + kf(t, [(1.85, 18), (2.25, 0)], ease_out), a)
# ---- Retro Action: chrome title, a star glint, one VHS tracking line ----------------------------------------
def draw_retro(self, canvas, pts, t):
@@ -247,14 +273,14 @@ class Block:
u = kf(t, [(2.55, 0), (2.95, 1)], lambda v: v)
if 0 < u < 1:
band_y = round(-30 + (H + 60) * u)
strip = canvas.crop((0, max(0, band_y), W, min(H, band_y + 18)))
strip = canvas.crop((0, max(0, band_y), W, min(H, band_y + 10)))
if strip.height > 0:
rng = random.Random(int(t * FPS))
shifted = Image.new('RGBA', strip.size)
for row in range(strip.height):
line = strip.crop((0, row, W, row + 1))
shifted.paste(line, (rng.randint(-14, 14), row))
bright = Image.new('RGBA', strip.size, (255, 255, 255, 40))
shifted.paste(line, (rng.randint(-5, 5), row))
bright = Image.new('RGBA', strip.size, (255, 255, 255, 16)) # Paul's 5% note: subtle, so the road stays the star
shifted.alpha_composite(bright)
canvas.paste(shifted, (0, max(0, band_y)))
@@ -299,7 +325,7 @@ class Block:
hop = kf(t, [(at, 26), (at + .16, -6), (at + .26, 0)], ease_out)
turned = layer.rotate(w, Image.BICUBIC, expand=True)
place(canvas, turned, tx + x - (turned.width - layer.width) / 2, ty + y + hop - (turned.height - layer.height) / 2, a)
perches = [(tx + title[1][1] + 20, ty + title[1][2] - 6), (tx + title[6][1] + 30, ty + title[6][2] - 4), (tx + title[11][1] + 18, ty + title[11][2] - 6)]
perches = [(tx + title[1][1] + 28, ty + title[1][2] + 6), (tx + title[6][1] + 38, ty + title[6][2] + 8), (tx + title[11][1] + 26, ty + title[11][2] + 6)] # on the letters' tops (they carry an 8 px shadow margin)
p = pts['planet']
px, py = (p['left'] + p['right']) / 2, (p['top'] + p['bottom']) / 2
for i, (lx, ly) in enumerate(perches):
@@ -337,7 +363,25 @@ def tartan(w, h, scale=3):
for x in range(0, w, L):
for y in range(0, h, L):
cloth.paste(tile, (x, y))
# Paul's 5% note, more depth: a woven grain, light catching the top fold and shade under it, like real cloth.
rng = random.Random(9)
grain = Image.new('L', (w, h))
gd = ImageDraw.Draw(grain)
for y in range(h):
for x in range(0, w, 2):
gd.point((x + (y % 2), y), fill=rng.randint(0, 255))
grain = grain.filter(ImageFilter.GaussianBlur(.6))
cloth = Image.blend(cloth, ImageChops.overlay(cloth, Image.merge('RGB', (grain, grain, grain))), .35)
shade = Image.new('L', (1, h))
for y in range(h):
v = y / (h - 1)
shade.putpixel((0, y), round(255 * (1.12 - .32 * v + .1 * math.sin(v * math.pi * 3))) if v < 1 else 200)
cloth = ImageChops.multiply(cloth, Image.merge('RGB', [shade.resize((w, h))] * 3).point(lambda q: min(255, q)))
highlight = Image.new('RGBA', (w, h))
hd = ImageDraw.Draw(highlight)
hd.rectangle((0, 4, w, 10), fill=(255, 255, 255, 46)); hd.rectangle((0, h - 12, w, h - 4), fill=(0, 0, 0, 70))
cloth = cloth.convert('RGBA')
cloth.alpha_composite(highlight)
mask = Image.new('L', (w, h), 0)
d = ImageDraw.Draw(mask)
d.rectangle((0, 4, w, h - 4), fill=235)
@@ -396,7 +440,15 @@ def make_ghost():
def make_bat(flap):
"""A tiny bat: wings beating (flap from -1 to 1), or folded when resting (None)."""
"""A tiny bat with a thin lilac rim so it reads against the night: wings beating (flap from -1 to 1), or folded when resting (None)."""
shape = draw_bat(flap)
rim = Image.new('RGBA', shape.size, (170, 130, 230, 0))
rim.putalpha(shape.getchannel('A').filter(ImageFilter.MaxFilter(3)))
rim.alpha_composite(shape)
return rim
def draw_bat(flap):
b = Image.new('RGBA', (48, 40))
d = ImageDraw.Draw(b)
body = (18, 10, 32)
@@ -421,9 +473,16 @@ def wobbly(words):
x += 26
continue
face = font('comfortaa.ttf', rng.choice((70, 74, 78)))
letter = text_layer(c, face, (255, 154, 60, 255), 0, 4, (44, 16, 70))
out.append((letter, x, rng.uniform(-8, 8), rng.uniform(-7, 7)))
x += letter.width - 4
letter = text_layer(c, face, (255, 154, 60, 255), 0, 6, (36, 12, 58))
# Paul's 5% note: enough contrast on any screen, so a soft dark shadow sits behind each letter.
pad = 8
backed = Image.new('RGBA', (letter.width + 2 * pad, letter.height + 2 * pad))
under = Image.new('RGBA', letter.size, (10, 4, 20, 0)); under.putalpha(letter.getchannel('A').point(lambda v: v * 200 // 255))
backed.alpha_composite(under, (pad + 3, pad + 4)); backed = backed.filter(ImageFilter.GaussianBlur(4))
backed.alpha_composite(letter, (pad, pad))
letter = backed
out.append((letter, x - pad, rng.uniform(-8, 8) - pad, rng.uniform(-7, 7)))
x += letter.width - 4 - 2 * pad
return out
@@ -8,10 +8,10 @@
720
],
"crf": "34",
"identBytes": 163575,
"identBytes": 158642,
"ssim": {
"mean": 0.9608,
"worst": 0.9525
"mean": 0.9612,
"worst": 0.9526
},
"guideAppBytes": 5656,
"finalHoldStillForHalfSecond": true,
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 MiB

After

Width:  |  Height:  |  Size: 1.7 MiB

@@ -8,16 +8,16 @@
720
],
"crf": "34",
"identBytes": 151640,
"identBytes": 160457,
"ssim": {
"mean": 0.9609,
"mean": 0.9606,
"worst": 0.9505
},
"guideAppBytes": 6348,
"finalHoldStillForHalfSecond": true,
"flash": {
"frames": 120,
"largestFrameChange": 0.0144,
"largestFrameChange": 0.0145,
"framesChangingOver10Percent": 0,
"brightnessReversals": 0
},
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 MiB

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 866 KiB

After

Width:  |  Height:  |  Size: 860 KiB

@@ -8,9 +8,9 @@
720
],
"crf": "34",
"identBytes": 172581,
"identBytes": 173193,
"ssim": {
"mean": 0.9581,
"mean": 0.958,
"worst": 0.9445
},
"guideAppBytes": 6150,
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 702 KiB

After

Width:  |  Height:  |  Size: 700 KiB

@@ -8,16 +8,16 @@
720
],
"crf": "34",
"identBytes": 171705,
"identBytes": 168879,
"ssim": {
"mean": 0.9653,
"worst": 0.9564
"mean": 0.9654,
"worst": 0.9568
},
"guideAppBytes": 6604,
"finalHoldStillForHalfSecond": true,
"flash": {
"frames": 120,
"largestFrameChange": 0.0071,
"largestFrameChange": 0.007,
"framesChangingOver10Percent": 0,
"brightnessReversals": 0
},
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 826 KiB

After

Width:  |  Height:  |  Size: 844 KiB

@@ -8,9 +8,9 @@
720
],
"crf": "34",
"identBytes": 494160,
"identBytes": 458426,
"ssim": {
"mean": 0.9399,
"mean": 0.9404,
"worst": 0.8881
},
"guideAppBytes": 7108,
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 MiB

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 671 KiB

After

Width:  |  Height:  |  Size: 604 KiB