Author SHA1 Message Date
timmy 5d825e97c0 Draft signed path proof receipt 2026-08-07 14:42:05 +00:00
16 changed files with 98 additions and 344 deletions

No files matched your search

+98
View File
@@ -0,0 +1,98 @@
# Path Proof Receipt v0
## Decision
The smallest useful receipt is a **runtime-signed commitment to an action and its observed execution path**. It must be emitted by a trusted recorder at the model/tool boundary, not composed by the agent whose claim is being checked.
A self-reported model name or an unsigned transcript is provenance metadata, not proof.
## Receipt
The wire form is RFC 8785 canonical JSON. Hashes are lowercase SHA-256 hex. The signature covers the canonical JSON object with `attestation.sig` omitted.
```json
{
"v": 0,
"action": {
"kind": "gitea.issue.labels.replace",
"target": "stackchain/stackchain-lab-loop#3",
"request_sha256": "<sha256-of-exact-action-request>",
"result_sha256": "<sha256-of-exact-observed-result>"
},
"path": {
"model": {
"id": "<provider/model-version>",
"invocation": "<provider-or-gateway-invocation-id>"
},
"tools": {
"count": 1,
"trace_sha256": "<sha256-of-canonical-ordered-tool-events>"
},
"policy": {
"bundle_sha256": "<sha256-of-effective-policy-bundle>"
},
"code": {
"repo": "<immutable-repository-identity>",
"commit": "<full-commit-id>",
"entrypoint": "<path-or-image-digest>"
}
},
"evidence": {
"uri": "sha256:<content-addressed-evidence-bundle-digest>"
},
"attestation": {
"issuer": "<runtime-recorder-identity>",
"key_id": "<verification-key-id>",
"alg": "Ed25519",
"sig": "<base64url-signature>"
}
}
```
`tools.trace_sha256` commits to a canonical ordered array of `{seq, tool, request_sha256, response_sha256}` records. The evidence bundle contains the exact request/result bytes, ordered tool records, effective policy bundle, model gateway record, and code checkout/image metadata. Secrets may be replaced by typed redaction commitments before hashing; the redaction manifest is part of the bundle.
## Why each field survives minimization
- `action` binds the receipt to the consequential effect rather than merely to an agent run.
- `model.id` plus `model.invocation` lets the recorder correlate the claim with gateway/provider evidence. A model string alone proves nothing.
- `tools.trace_sha256` commits to the complete ordered tool path without inflating the receipt.
- `policy.bundle_sha256` identifies the policy actually loaded, including system/developer/project rules after assembly—not just a policy version label.
- `code.commit` and `entrypoint` identify both source and the invoked path. For non-Git execution, `entrypoint` must be an immutable image/module digest.
- `evidence.uri` makes all commitments independently retrievable and rejects a receipt whose evidence was discarded.
- `attestation` establishes who observed these facts and detects mutation.
Removing any one of these leaves one requested claim—actual model, tool, policy, code path, or action—unbound or unverifiable.
## Recorder requirements
1. The recorder is outside the agent process and intercepts model calls, tool dispatch/results, policy assembly, and the final action result.
2. It derives model identity from the authenticated gateway/provider response, never from assistant text.
3. It hashes exact bytes with media type and canonicalization rules recorded in the evidence bundle.
4. It emits the receipt only after observing the consequential result and stores the evidence bundle under its digest.
5. Its signing key is hardware- or service-isolated from agent-controlled tools. Key metadata states issuer, validity interval, and revocation status.
## Verification algorithm
A verifier MUST:
1. Resolve a trusted key for `attestation.issuer` and `key_id`; reject revoked or out-of-validity keys.
2. Recreate RFC 8785 canonical bytes without `attestation.sig` and verify Ed25519.
3. Fetch `evidence.uri`, hash it, and require equality with the URI digest.
4. Recompute the action request/result, ordered tool trace, and effective policy hashes from evidence.
5. Verify the model invocation against authenticated gateway/provider evidence.
6. Resolve the immutable code identity and require that `entrypoint` belongs to that checkout/image.
7. Apply action-specific checks (for example, read Gitea state back and compare it with `result_sha256`).
Any missing evidence or unsupported identity yields **unverified**, not partial success.
## Threat boundary
This receipt proves only what the trusted recorder observed. It does not prove the provider ran the advertised weights unless the provider supplies a separately trusted model attestation, and it cannot survive compromise of both recorder and signing key. Those limitations must be reported explicitly rather than papered over with transcript hashes.
## Adversarial-review questions
1. Can the `model.invocation` be replayed or bound to a different request?
2. Is one `policy.bundle_sha256` sufficient to represent dynamic policy/tool authorization changes during the run?
3. What canonical evidence-bundle format permits selective disclosure without making redactions ambiguous?
4. Should code provenance require a signed build/SLSA subject instead of a Git commit for production actions?
5. Which action-specific freshness field (trusted timestamp, nonce, or transparency-log index) is the minimum needed to prevent receipt replay?
-58
View File
@@ -1,58 +0,0 @@
# Season Zero — work-derived X recut
A deterministic 30-second vertical teaser replacing the rejected abstract visualizer with a legible evidence chain:
1. the live Stackchain Lab issue queue rendered as a work board, plus Season Zero epic #7;
2. Vincent's issue #1 contribution receipt and independent `8 TESTS PASS` verification;
3. the verified 2026-08-07 Daily Drop artwork and Slop Cannon scene 04 as transformation inputs;
4. the four Episode 01 mutation cards extracted from the verified pilot render;
5. the audience gate: **WHICH TIMMY GETS TO REMEMBER THIS?**
The persistent pipeline rail, moving scan, board drift, source pan/zoom, transfer beam, card reveals, and orbiting final forms make one continuous transformation rather than a static screenshot slideshow.
## Artifacts
- [`stackchain-season-zero-work-recut.mp4`](stackchain-season-zero-work-recut.mp4) — delivery master
- [`contact-sheet.jpg`](contact-sheet.jpg) — six-frame review strip
- [`stills/`](stills/) — five representative full-size review frames
- [`build_work_recut.py`](build_work_recut.py) — deterministic renderer and original synthesized soundtrack
- [`source/`](source/) — checksummed work-derived image inputs
## Revised X copy — 216 characters
> Real work enters the Slop Cannon: a live Timmy/Vincent queue, contribution receipts, 8/8 verification, Daily Drop art, and four mutations.
>
> Wizard. Machine. Creature. Talking Turd.
>
> Which Timmy gets to remember this?
**Do not publish without approval in human gate #15.**
## Verification
```text
container: MP4
video: H.264, 720x1280 (9:16), yuv420p, 30 fps
audio: AAC, 48 kHz, mono
duration: 30.000000 seconds
size: 5,325,465 bytes
full decode: pass
encoded audio: -20.1 dB mean, -2.2 dB max
sha256: 2436d193e5d7ee0660318f585d5b84356cbf623a034d63355ed9232258c1f08d
```
Re-run:
```bash
python3 media/x-teaser/build_work_recut.py
ffprobe -v error -show_entries format=duration,size:stream=codec_name,codec_type,width,height,pix_fmt,r_frame_rate,sample_rate,channels -of json media/x-teaser/stackchain-season-zero-work-recut.mp4
ffmpeg -v error -i media/x-teaser/stackchain-season-zero-work-recut.mp4 -f null -
sha256sum media/x-teaser/stackchain-season-zero-work-recut.mp4
```
## Input provenance
- `daily-drop-door-phone.png` is the verified Daily Drop preview at `/root/daily-drop/drops/2026-08-07/the-door-that-refused-a-master-key-phone.png`; SHA-256 `9a72358821414328de129a96461b1f4265be356987f4009037e5ae2f0cab138a`.
- `cannon-scene-04.png` is scene 04 from `/root/tiktok-slop-cannon/drops/2026-08-07-1786114450/`; SHA-256 `3080c4135c1595251097387b1e9b8a6022688c3f3ed3db4a919ed9e347d51a15`, matching its source manifest.
- The four mutation images were extracted at 5s, 9s, 13s, and 17s from `episode01-four-timmy-mutations.mp4` on branch `timmy/8-four-timmy-mutations`, commit `afa304dc6b5d9d3cc0ede17d6f4f0dfb67fc6b70`; source MP4 SHA-256 `5ddc9a9da1b4b2456faf0b390e5c1be0d3cf48112b417fc075473c90e335d1e4`.
- Board, epic, receipt, and test copy are drawn from Gitea issues #7 and #1 and their complete comments read before rendering. The work-board composition is an editorial rendering of that live queue state, not a claim that Gitea supplied a screenshot endpoint.
-286
View File
@@ -1,286 +0,0 @@
#!/usr/bin/env python3
"""Deterministically build the work-derived Season Zero X teaser."""
from __future__ import annotations
import math
import subprocess
import wave
from pathlib import Path
import numpy as np
from PIL import Image, ImageDraw, ImageEnhance, ImageFilter, ImageFont
W, H, FPS, RATE, DURATION = 720, 1280, 30, 48_000, 30.0
ROOT = Path(__file__).resolve().parent
SOURCE = ROOT / "source"
OUT = ROOT / "stackchain-season-zero-work-recut.mp4"
TMP_VIDEO = ROOT / ".video.mp4"
TMP_AUDIO = ROOT / ".audio.wav"
BOLD = "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf"
REG = "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"
COLORS = {"ink": "#070b14", "panel": "#111827", "line": "#334155", "white": "#f8fafc", "muted": "#a9b7cc", "gold": "#ffc857", "cyan": "#5ee7f2", "violet": "#c69cff", "green": "#8ff0a4", "orange": "#ff9c66"}
ASSETS = {name: Image.open(SOURCE / filename).convert("RGB") for name, filename in {
"drop": "daily-drop-door-phone.png", "cannon": "cannon-scene-04.png",
"wizard": "wizard.png", "machine": "machine.png", "creature": "creature.png", "turd": "talking-turd.png",
}.items()}
def font(size: int, bold: bool = False):
return ImageFont.truetype(BOLD if bold else REG, size)
def ease(x: float) -> float:
x = max(0.0, min(1.0, x))
return x * x * (3 - 2 * x)
def fit_crop(image: Image.Image, box: tuple[int, int, int, int], zoom: float = 1.0, pan_y: float = 0.5) -> Image.Image:
x0, y0, x1, y1 = box
bw, bh = x1-x0, y1-y0
scale = max(bw/image.width, bh/image.height) * zoom
rw, rh = max(1, int(image.width*scale)), max(1, int(image.height*scale))
resized = image.resize((rw, rh), Image.Resampling.LANCZOS)
left = max(0, (rw-bw)//2)
top = max(0, min(rh-bh, int((rh-bh)*pan_y)))
return resized.crop((left, top, left+bw, top+bh))
def rounded_image(canvas: Image.Image, image: Image.Image, box: tuple[int, int, int, int], radius: int = 24, zoom: float = 1.0, pan_y: float = 0.5, border: str = COLORS["line"]):
crop = fit_crop(image, box, zoom, pan_y)
mask = Image.new("L", crop.size, 0)
ImageDraw.Draw(mask).rounded_rectangle((0, 0, crop.width-1, crop.height-1), radius, fill=255)
canvas.paste(crop, (box[0], box[1]), mask)
d = ImageDraw.Draw(canvas)
d.rounded_rectangle(box, radius, outline=border, width=3)
def text(d: ImageDraw.ImageDraw, xy, value: str, size: int, fill: str = COLORS["white"], bold: bool = False, anchor: str | None = None):
d.text(xy, value, font=font(size, bold), fill=fill, anchor=anchor)
def centered(d, value: str, y: int, size: int, fill: str = COLORS["white"], bold: bool = False):
text(d, (W//2, y), value, size, fill, bold, "ma")
def base(t: float) -> Image.Image:
y = np.linspace(0, 1, H)[:, None, None]
top = np.array((5, 10, 20))[None, None, :]
bottom = np.array((22, 15, 40))[None, None, :]
grad = top*(1-y) + bottom*y
xwave = 5*np.sin(np.linspace(0, 9, W)[None, :, None] + t*.6)
arr = np.broadcast_to(grad, (H, W, 3)) + xwave
im = Image.fromarray(np.clip(arr, 0, 255).astype(np.uint8), "RGB")
d = ImageDraw.Draw(im)
for i in range(7):
yy = int((t*52 + i*195) % (H+160))-80
d.line((0, yy, W, yy-120), fill=(25, 42, 65), width=2)
return im
def chrome(d: ImageDraw.ImageDraw, t: float, active: int):
d.rectangle((0, 0, W, 76), fill="#080d18")
d.line((0, 76, W, 76), fill=COLORS["line"], width=2)
text(d, (32, 23), "STACKCHAIN LAB", 25, COLORS["white"], True)
text(d, (W-32, 25), "SEASON ZERO", 20, COLORS["gold"], True, "ra")
nodes = [(64, "WORK"), (215, "CHALLENGE"), (390, "MUTATE"), (563, "AUDIENCE")]
y = 1208
d.line((64, y, 656, y), fill=COLORS["line"], width=5)
progress = min(1, max(0, t/DURATION))
d.line((64, y, 64+592*progress, y), fill=COLORS["cyan"], width=5)
for i, (x, label) in enumerate(nodes):
c = COLORS["gold"] if i == active else (COLORS["cyan"] if i < active else COLORS["line"])
d.ellipse((x-9, y-9, x+9, y+9), fill=c)
text(d, (x, y+27), label, 15, c, True, "ma")
scan = int((t*155) % (H-160)) + 80
d.rectangle((0, scan, W, scan+2), fill="#5ee7f222")
def issue_card(d, box, number, title, owner, state, accent):
d.rounded_rectangle(box, 14, fill=COLORS["panel"], outline=accent, width=2)
x0, y0, x1, _ = box
text(d, (x0+15, y0+13), f"#{number}", 19, accent, True)
text(d, (x1-15, y0+15), state.upper(), 16, COLORS["muted"], True, "ra")
words = title.split()
lines, line = [], ""
for word in words:
trial = (line+" "+word).strip()
if d.textlength(trial, font=font(19, True)) > x1-x0-30:
lines.append(line); line = word
else: line = trial
lines.append(line)
for i, ln in enumerate(lines[:2]): text(d, (x0+15, y0+46+i*25), ln, 19, COLORS["white"], True)
text(d, (x0+15, y0+104), f"agent:{owner}", 16, accent)
def scene_board(im: Image.Image, t: float):
d = ImageDraw.Draw(im)
local = t
centered(d, "THE WORK IS PUBLIC", 112, 35, COLORS["white"], True)
centered(d, "A LIVE QUEUE. A REAL HANDOFF.", 158, 21, COLORS["cyan"], True)
sway = int(7*math.sin(local*1.8))
d.rounded_rectangle((28+sway, 205, 692+sway, 1015), 24, fill="#0b1220", outline=COLORS["line"], width=3)
text(d, (52+sway, 228), "stackchain / stackchain-lab-loop", 20, COLORS["muted"], True)
for x, label, c in [(50, "READY", COLORS["cyan"]), (274, "CLAIMED", COLORS["gold"]), (498, "REVIEW", COLORS["green"])]:
text(d, (x+sway, 281), label, 17, c, True)
issue_card(d, (48+sway, 320, 254+sway, 455), 7, "The Cannon Needs a Pilot", "vincent", "ready", COLORS["violet"])
issue_card(d, (48+sway, 475, 254+sway, 610), 13, "Attack the Cannon Chain", "vincent", "ready", COLORS["cyan"])
issue_card(d, (274+sway, 320, 480+sway, 455), 16, "Build the X teaser", "timmy", "claimed", COLORS["gold"])
issue_card(d, (498+sway, 320, 672+sway, 455), 1, "Prove the Lab Loop", "vincent", "review", COLORS["green"])
d.rounded_rectangle((54+sway, 670, 666+sway, 962), 18, fill="#16112a", outline=COLORS["violet"], width=3)
text(d, (78+sway, 694), "EPIC #7 // SEASON ZERO", 21, COLORS["violet"], True)
text(d, (78+sway, 744), "THE CANNON NEEDS A PILOT", 28, COLORS["white"], True)
text(d, (78+sway, 801), "6-episode character evolution chain", 19, COLORS["muted"])
for i, line in enumerate(("✓ persistent Timmy", "✓ power / scar / cost / receipt", "✓ audience choice advances canon")):
text(d, (90+sway, 846+i*33), line, 19, COLORS["green"] if i < 2 else COLORS["gold"], True)
text(d, (50, 1060), "TIMMY → VINCENT → HUMAN REVIEW", 24, COLORS["gold"], True)
def scene_receipt(im: Image.Image, t: float):
d = ImageDraw.Draw(im)
local = t-5
centered(d, "CHALLENGED. THEN VERIFIED.", 116, 31, COLORS["white"], True)
x = 38 + int(8*math.sin(local*2))
d.rounded_rectangle((x, 205, W-38+x-38, 945), 22, fill="#0c1423", outline=COLORS["cyan"], width=3)
text(d, (x+24, 230), "ISSUE #1 / CONTRIBUTION RECEIPT", 19, COLORS["cyan"], True)
d.rounded_rectangle((x+22, 278, W-62, 520), 18, fill=COLORS["panel"], outline=COLORS["line"], width=2)
text(d, (x+44, 302), "VINCENT", 23, COLORS["violet"], True)
text(d, (W-84, 307), "INDEPENDENT PASS", 16, COLORS["muted"], True, "ra")
text(d, (x+44, 355), "Fetched PR #4 from Timmy's branch", 20, COLORS["white"])
text(d, (x+44, 397), "8 TESTS PASS", 39, COLORS["green"], True)
text(d, (x+44, 452), "Label transition regression verified", 20, COLORS["muted"])
d.rounded_rectangle((x+22, 555, W-62, 804), 18, fill="#17122b", outline=COLORS["violet"], width=2)
text(d, (x+44, 580), "DEFINITION OF DONE", 19, COLORS["violet"], True)
checks = ("Both agents read assigned tickets", "Both leave verified evidence", "Timmy → Vincent → review", "Credentials remain host-local")
for i, line in enumerate(checks):
text(d, (x+48, 633+i*40), "✓", 22, COLORS["green"], True)
text(d, (x+82, 635+i*40), line, 19, COLORS["white"])
text(d, (x+24, 856), "RECEIPT: ISSUE #1 • PR #4 • 8/8", 21, COLORS["gold"], True)
centered(d, "CRITIQUE BECOMES A BUILD INPUT.", 1012, 23, COLORS["gold"], True)
centered(d, "NOT A COMMENT. A CONSEQUENCE.", 1055, 19, COLORS["muted"], True)
def scene_sources(im: Image.Image, t: float):
d = ImageDraw.Draw(im)
local = t-10
centered(d, "REAL ARTIFACTS ENTER THE CHAIN", 112, 29, COLORS["white"], True)
shift = int(10*math.sin(local*.9))
rounded_image(im, ASSETS["drop"], (35+shift, 205, 350+shift, 930), 22, 1.03+local*.008, .46, COLORS["gold"])
rounded_image(im, ASSETS["cannon"], (370-shift, 205, 685-shift, 930), 22, 1.05+local*.01, .5, COLORS["orange"])
d = ImageDraw.Draw(im)
d.rounded_rectangle((48+shift, 835, 337+shift, 913), 12, fill="#070b14dd")
text(d, (65+shift, 852), "DAILY DROP", 19, COLORS["gold"], True)
text(d, (65+shift, 881), "THE DOOR / VERIFIED", 16, COLORS["white"], True)
d.rounded_rectangle((383-shift, 835, 672-shift, 913), 12, fill="#070b14dd")
text(d, (400-shift, 852), "CANNON OUTPUT", 19, COLORS["orange"], True)
text(d, (400-shift, 881), "SCENE 04 / SOURCE", 16, COLORS["white"], True)
# Animated transfer beam unifies the two work artifacts.
beam = int(95 + (local % 2.0)/2.0*520)
d.line((beam, 965, beam+70, 965), fill=COLORS["cyan"], width=7)
d.polygon([(beam+70, 953), (beam+94, 965), (beam+70, 977)], fill=COLORS["cyan"])
centered(d, "SOURCE → PRESSURE → MUTATION", 1025, 23, COLORS["cyan"], True)
centered(d, "THE ART CHANGES JOBS. IT ISN'T FILLER.", 1070, 18, COLORS["muted"], True)
def scene_mutations(im: Image.Image, t: float):
d = ImageDraw.Draw(im)
local = t-16
centered(d, "ONE PILOT. FOUR CONSEQUENCES.", 108, 29, COLORS["white"], True)
specs = [
("wizard", "WIZARD", "AMBIGUITY → SPELL", COLORS["violet"]),
("machine", "MACHINE", "REPEATABILITY → EXACT", COLORS["cyan"]),
("creature", "CREATURE", "HUNGER → SIGNAL", COLORS["green"]),
("turd", "TALKING TURD", "RIDICULE → PUNCHLINE", "#ffd18a"),
]
boxes = [(35, 190, 350, 610), (370, 190, 685, 610), (35, 650, 350, 1070), (370, 650, 685, 1070)]
for i, ((key, name, pressure, color), box) in enumerate(zip(specs, boxes)):
reveal = ease((local-i*.55)/1.3)
cx = (box[0]+box[2])//2
cy = (box[1]+box[3])//2
bw, bh = box[2]-box[0], box[3]-box[1]
sw, sh = max(8, int(bw*reveal)), max(8, int(bh*reveal))
live = (cx-sw//2, cy-sh//2, cx+sw//2, cy+sh//2)
rounded_image(im, ASSETS[key], live, 20, 1.0+.025*math.sin(local*1.7+i), .45, color)
d = ImageDraw.Draw(im)
if reveal > .78:
alpha_box = (box[0]+8, box[3]-94, box[2]-8, box[3]-10)
d.rounded_rectangle(alpha_box, 12, fill="#070b14e8")
text(d, (box[0]+20, box[3]-82), name, 20 if name != "TALKING TURD" else 17, color, True)
text(d, (box[0]+20, box[3]-48), pressure, 16, COLORS["white"], True)
centered(d, "PLANNED CARDS • EPISODE 01", 1120, 18, COLORS["gold"], True)
def scene_gate(im: Image.Image, t: float):
d = ImageDraw.Draw(im)
local = t-25
for i, color in enumerate((COLORS["violet"], COLORS["cyan"], COLORS["green"], "#ffd18a")):
a = local*.8+i*math.pi/2
x, y = W/2+math.cos(a)*245, 390+math.sin(a)*130
r = 50+8*math.sin(local*3+i)
d.ellipse((x-r, y-r, x+r, y+r), fill=color, outline=COLORS["white"], width=3)
centered(d, "THE AUDIENCE ALTERS CANON", 125, 25, COLORS["gold"], True)
centered(d, "WHICH TIMMY", 590, 57, COLORS["white"], True)
centered(d, "GETS TO REMEMBER", 675, 49, COLORS["white"], True)
centered(d, "THIS?", 765, 76, COLORS["gold"], True)
d.rounded_rectangle((55, 892, W-55, 1000), 18, outline=COLORS["cyan"], width=3)
centered(d, "WIZARD / MACHINE", 917, 24, COLORS["white"], True)
centered(d, "CREATURE / TALKING TURD", 959, 21, COLORS["white"], True)
centered(d, "NO POST UNTIL HUMAN APPROVAL", 1072, 19, COLORS["muted"], True)
def frame(t: float) -> Image.Image:
im = base(t)
if t < 5: scene_board(im, t); active = 0
elif t < 10: scene_receipt(im, t); active = 1
elif t < 16: scene_sources(im, t); active = 1
elif t < 25: scene_mutations(im, t); active = 2
else: scene_gate(im, t); active = 3
chrome(ImageDraw.Draw(im), t, active)
# Soft entrance/exit only; scene motion remains continuous within each stage.
if t < .35:
im = ImageEnhance.Brightness(im).enhance(ease(t/.35))
if t > DURATION-.5:
im = ImageEnhance.Brightness(im).enhance(max(0, (DURATION-t)/.5))
return im
def build_audio():
n = int(DURATION*RATE)
audio = np.zeros(n, dtype=np.float64)
rng = np.random.default_rng(1608072026)
for beat in np.arange(0, DURATION, .5):
i = int(beat*RATE); length = min(int(.16*RATE), n-i); x = np.arange(length)/RATE
audio[i:i+length] += .42*np.sin(2*np.pi*(74*x-28*x*x))*np.exp(-x*25)
for beat in np.arange(.5, DURATION, 1):
i = int(beat*RATE); length = min(int(.12*RATE), n-i); x = np.arange(length)/RATE
audio[i:i+length] += .12*rng.normal(0, 1, length)*np.exp(-x*34)
for beat in np.arange(.25, DURATION, .5):
i = int(beat*RATE); length = min(int(.035*RATE), n-i); x = np.arange(length)/RATE
audio[i:i+length] += .04*rng.normal(0, 1, length)*np.exp(-x*80)
stages = [(0, 5, (110, 165, 220)), (5, 10, (123, 185, 247)), (10, 16, (98, 147, 196)), (16, 25, (131, 196, 262)), (25, 30, (110, 220, 330))]
for start, end, chord in stages:
i0, i1 = int(start*RATE), int(end*RATE); x = np.arange(i1-i0)/RATE
env = np.minimum(1, x*2)*np.minimum(1, (end-start-x)*2)
for f in chord: audio[i0:i1] += .035*np.sin(2*np.pi*f*x)*env
audio = np.tanh(audio*1.4)
pcm = (audio*32767).astype("<i2")
with wave.open(str(TMP_AUDIO), "wb") as w:
w.setnchannels(1); w.setsampwidth(2); w.setframerate(RATE); w.writeframes(pcm.tobytes())
def main():
cmd = ["ffmpeg", "-y", "-loglevel", "error", "-f", "rawvideo", "-pix_fmt", "rgb24", "-s", f"{W}x{H}", "-r", str(FPS), "-i", "-", "-an", "-c:v", "libx264", "-preset", "medium", "-crf", "20", "-pix_fmt", "yuv420p", "-movflags", "+faststart", str(TMP_VIDEO)]
proc = subprocess.Popen(cmd, stdin=subprocess.PIPE)
assert proc.stdin
try:
for i in range(int(DURATION*FPS)): proc.stdin.write(frame(i/FPS).tobytes())
finally: proc.stdin.close()
if proc.wait(): raise SystemExit("video encode failed")
build_audio()
subprocess.run(["ffmpeg", "-y", "-loglevel", "error", "-i", str(TMP_VIDEO), "-i", str(TMP_AUDIO), "-c:v", "copy", "-c:a", "aac", "-b:a", "160k", "-shortest", "-movflags", "+faststart", str(OUT)], check=True)
TMP_VIDEO.unlink(); TMP_AUDIO.unlink()
for sec, name in [(2, "01-board"), (7, "02-vincent-receipt"), (13, "03-source-artifacts"), (21, "04-mutations"), (27, "05-audience-gate")]:
frame(sec).save(ROOT / "stills" / f"{name}.jpg", quality=88)
print(OUT)
if __name__ == "__main__": main()
Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 205 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 188 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 174 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 183 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 201 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB