Skip to content

Add the LinkedIn marketing video, rendered from code - #838

Draft
0-jake-0 wants to merge 13 commits into
mainfrom
claude/wingfoil-linkedin-video-x8wm4r
Draft

Add the LinkedIn marketing video, rendered from code#838
0-jake-0 wants to merge 13 commits into
mainfrom
claude/wingfoil-linkedin-video-x8wm4r

Conversation

@0-jake-0

Copy link
Copy Markdown
Contributor

A 1080x1080, ~36s film for LinkedIn's muted mobile feed, carrying one
message: one graph definition runs as both an instant historical backtest
and a live realtime system, so backtest and production can't drift.

Everything on screen is code in this repo or output captured from running
it. Two rules hold the pipeline together:

  • Pacing follows the voice. build-voice.py synthesises one wav per
    scene with piper (LibriTTS-R, offline) and measures it; scene frame
    counts are derived from those durations and typed nowhere. That is the
    seam for dropping in a human voiceover later.
  • Terminal output is captured, never written. capture-output.sh runs
    the odds_evens example once per run mode and records what it printed,
    along with the command that printed it. It fails the build if the two
    modes ever stop producing identical values, because that is the claim
    the video makes.

Scenes 4 and 5 show what the engine actually does rather than the tidier
story. Under HistoricalFrom(NanoTime::ZERO) engine time is pure logic, so
the replay stamps 0.000_000 onward; under RealTime engine time is the
wall clock, so the same six ticks stamp as epoch nanoseconds. The
timestamps are not identical across modes and the video does not say they
are. What is identical, byte for byte, is the values and their order — so
scene 5 dims the clock column and holds the value column bright, and the
badge reads "same values, same order".

odds_evens grows a run-mode argument to back that, following the
convention run_mode already sets. The graph above it is wired once and
does not know which mode it got, which is the point the example was
already making and now demonstrates.

Deliverables land in marketing/linkedin-video/out/: the MP4 and a
sidecar SRT generated from the same measured timings as the burned-in
karaoke captions, so the two cannot drift.

Co-Authored-By: Claude Opus 5 noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_011boKQCBTFCkTZijPdPdnL1

claude added 10 commits August 15, 2026 14:29
A 1080x1080, ~36s film for LinkedIn's muted mobile feed, carrying one
message: one graph definition runs as both an instant historical backtest
and a live realtime system, so backtest and production can't drift.

Everything on screen is code in this repo or output captured from running
it. Two rules hold the pipeline together:

- **Pacing follows the voice.** `build-voice.py` synthesises one wav per
  scene with piper (LibriTTS-R, offline) and measures it; scene frame
  counts are derived from those durations and typed nowhere. That is the
  seam for dropping in a human voiceover later.
- **Terminal output is captured, never written.** `capture-output.sh` runs
  the `odds_evens` example once per run mode and records what it printed,
  along with the command that printed it. It fails the build if the two
  modes ever stop producing identical values, because that is the claim
  the video makes.

Scenes 4 and 5 show what the engine actually does rather than the tidier
story. Under `HistoricalFrom(NanoTime::ZERO)` engine time is pure logic, so
the replay stamps 0.000_000 onward; under `RealTime` engine time *is* the
wall clock, so the same six ticks stamp as epoch nanoseconds. The
timestamps are not identical across modes and the video does not say they
are. What is identical, byte for byte, is the values and their order — so
scene 5 dims the clock column and holds the value column bright, and the
badge reads "same values, same order".

`odds_evens` grows a run-mode argument to back that, following the
convention `run_mode` already sets. The graph above it is wired once and
does not know which mode it got, which is the point the example was
already making and now demonstrates.

Deliverables land in `marketing/linkedin-video/out/`: the MP4 and a
sidecar SRT generated from the same measured timings as the burned-in
karaoke captions, so the two cannot drift.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011boKQCBTFCkTZijPdPdnL1
The film was built on a placeholder teal/amber palette and an invented
diamond glyph. Both are replaced with the actual brand: the hexagonal-spiral
mark from the Sept 2025 deck in `wingfoil-io/assets`, and the two colours
sampled from its gradient — #FF31C9 magenta and #2C98FF blue, meeting at
#5D78FF.

The split earns its keep rather than just matching. **Magenta is live** —
the wall clock, the drift, the thing that moves. **Blue is the engine** —
the graph, the replay, the thing that doesn't. So the hook's two rules are
magenta and blue drifting apart, scene 3 gives one branch each colour and
makes `merge` the indigo between them, scenes 4 and 5 colour the
engine-time column by run mode, and the payoff converges the two rules into
a single rule painted in the brand gradient — the same sweep the logo is
drawn in.

Scenes 2–5 lose their headings. Each one mostly restated the caption
underneath it, and dropping them buys the caption band 29px -> 38px, which
is the size that matters for someone watching muted on a phone. The badges
stay: they carry the run-mode claim rather than repeating the narration.

wingfoil.io itself is blocked by the sandbox's egress proxy, so the brand
came from the assets repo instead. `src/theme.ts` is the single place three
hexes and one PNG define the look, and the README says so, so re-sampling
against the live site is a two-file change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011boKQCBTFCkTZijPdPdnL1
The pills under scenes 4 and 5 ("backtest · instant · deterministic",
"live · wall-clock · same values, same order") were a third voice saying
what the narration and the caption already said. Scenes 2-5 are now the
visual and one caption band, nothing else.

What tells the two run modes apart is the evidence rather than a label on
it, which is stronger anyway: the command line carries `-- realtime`, the
engine-time column is blue and zero-based for the replay and magenta and
epoch for the live run, and only the live run has a `waiting…` prompt. With
the pills gone the terminals take the room instead — 25px to 27px on the
replay, 22px to 24px on the live run.

The code scene's narration now introduces what it is about to show: "For
example: split a counter into odds and evens, merge them back, then run
it." Scene lengths, the caption timings and the SRT all re-derived from the
new wav, as they are supposed to.

36.9s.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011boKQCBTFCkTZijPdPdnL1
The film demonstrated the mechanism on `1 is odd` / `2 is even`, which
proves the plumbing and nothing about the capability. For an audience of
quants the example *is* the argument, so it is now a real one.

Adds `examples/core/top_of_book`: real NASDAQ AAPL messages from the
LOBSTER sample, through a real limit order book, over a `channel` source.
That last part is what makes the video's claim demonstrable rather than
asserted — `channel` is the one source that works in both run modes, fed
with `send_at` for a deterministic replay and with `send` for a live feed,
so the identical graph really does run both ways. The producer is the only
code that knows which mode it is in.

The diamond is now load-bearing. `book` is a shared apex the engine runs
once per cycle however many readers it has — rebuilding a limit order book
per downstream path would be both wrong and expensive — and `bid` and `ask`
run at genuinely different frequencies, so `distinct` on each branch and a
`join` that fires when either moves is the natural wiring rather than a
contrivance.

Scene 3 now carries the README's published **~27 ns engine overhead per
node cycle**, opposite the clock and landing while the narration is on
"runs each node once per cycle". Latency is the differentiator this
audience asks about first and the film previously never mentioned it.

The payoff narrows from "backtest and live can't drift" to
"**implementations** can't drift". Code cannot drift and that is provable;
live-versus-backtest divergence is also driven by market impact, fills and
data revisions, which this does not solve. The narrower claim is the one
that survives contact with the audience.

Captures assert quote-for-quote equality across both modes over the whole
run, not just the six rows on screen. A live run reads the book at cycle
boundaries, so under load it can coalesce updates a replay resolves
separately; the README says so, and the assert is what stands behind the
wording.

40.8s.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011boKQCBTFCkTZijPdPdnL1
The example reached for `g.channel::<Message>()` directly and then matched
on `RunMode` inside one producer function. That works, but it puts the
plumbing where the seam should be: a reader has to follow the channel to
see what actually differs between a backtest and production.

`MarketData::connect` is that seam now. `Replay` stamps each message with
its own time and lets the engine schedule it; `LiveFeed` waits out the gap
each message originally arrived after and hands it over; `market_data`
picks one, and is the only place in the program that branches on run mode.
Both still deliver over a `channel` — that is the one source working in
either mode — but it is an implementation detail of the two impls rather
than the thing the example is about. A real feed implements the same trait
and nothing below `connect` changes.

This is the shape `run_mode` already introduces, applied to real data.

Scene 2 gains from it. The snippet now opens on

    // The only line that differs between backtest and live.
    let feed = market_data(run_mode)?.connect(&g)?;

which is the film's whole argument in one statement, where before the first
line was channel construction. `market_data` reads `SPAN_SECONDS` from the
const rather than taking it as a parameter, so the call on screen is exactly
the call in the example.

Also replaces the example README's sample output, which was written from a
placeholder before the example ran, with what it actually prints.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011boKQCBTFCkTZijPdPdnL1
The film read at 18-20 characters per second on six of its eight captioned
sentences. Netflix caps English subtitles at 17 CPS and the BBC lands near
the same place, and both assume a viewer whose only job is reading — this
one asks them to take in a code block or a live terminal at the same time,
so it was comfortably too fast for the muted viewer it is built for.

`scripts/check-pacing.py` now measures every captioned sentence over its
real on-screen window and fails the build over 15.5 CPS. `npm run build`
runs it between the voice build and the render. The film peaks at 14.5 now.

The fix was mostly `voice.lengthScale` 1.05 -> 1.37 and `sentenceSilence`
0.35 -> 0.7, plus a `hold` on the two scenes whose *final* sentence was the
tight one, and splitting the longest realtime line in two. Wording was
trimmed as well, but not for the reason it looks like: shortening a sentence
shrinks its window along with its text and barely moves CPS at all. Trimming
buys back length budget so the voice can slow without the film growing — the
failure message says so, because it is the first thing anyone reaching for
this will get wrong.

Also pins `voice.noiseWScale` to 0. VITS predicts phoneme durations
stochastically, so the same text synthesised twice differed by ~2% — enough
to re-time every scene, shift every caption and make the new gate flaky.
Pinned, `assets/narration.json` is byte-identical across runs, which the
pipeline needed anyway to deserve the word re-renderable.

45.8s, up from 40.9s. Past the 45s sweet spot by a whisker and well inside
the 60s cap — the right trade for captions a reader can actually finish.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011boKQCBTFCkTZijPdPdnL1
Scene 3 carried "~27 ns engine overhead per node cycle", which is the
weakest number the repo publishes: it is the interpreted engine's per-node
cost, and it says nothing about the tier that makes wingfoil interesting.

It now carries the nitro compiled figures from benches/README.md — 23.9 ns
of whole-graph overhead per cycle, 12× the interpreted tier, 1610× tokio
async streams. Those come from the depth-10 branch/recombine sweep, which is
the same fan-out-and-recombine shape the scene animates beside them.

Three things hold the claim honest, and all three are on screen. "Overhead"
is load-bearing: 23.9 ns is what the engine adds per cycle, not what a node's
own work costs, and maintaining a limit order book dwarfs it — the narration
says "of overhead" for the same reason. The workload is named, because
benches/README.md is emphatic that the ratio is the claim and the absolute
time is not, so quoting one without its workload quotes it wrong. And it is
not a claim about the graph in scenes 2, 4 and 5, which the scene never
implies.

The ratios take the indigo brand tone rather than magenta, which everywhere
else in the film means "live".

Narration and the pacing gate re-derived: `lengthScale` eases to 1.33 to pay
for the longer line, and the dag scene's `hold` goes back up because the gate
caught its final sentence at 16.8 CPS once the hold was trimmed. 46.5s.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011boKQCBTFCkTZijPdPdnL1
The claim was overreaching in two directions at once.

"1610× tokio async streams" is a real published figure, but it comes from a
depth-10 branch/recombine sweep — a workload built to expose per-path
propagation, so it flatters wingfoil by construction. A ratio like that means
nothing without its workload attached, a frame of video cannot carry that
much fine print, and this audience is right to read a bare 1610× as a
strawman. It is gone, along with any other third-party comparison: the tiers
stand on their own.

The remaining numbers are now *measured* rather than quoted.
`scripts/capture-bench.sh` runs the committed `fanout` group of
benches/tiers.rs, reads criterion's structured estimates, and writes
`assets/bench.json`; `src/bench.ts` renders from that file. Nothing on screen
is typed, and re-measuring on a quiet host is `npm run bench`. On this
machine: 32.0 ns per graph cycle compiled, against 1.57 µs interpreted.

Two judgement calls in what is *not* shown. The `legacy` bar the bench also
measures is an internal regression baseline, not a claim. And the figure is
per *graph* cycle rather than the 0.31 ns per node-cycle it divides to —
roughly one CPU cycle per node invites the correct suspicion that the
optimiser flattened part of a synthetic benchmark, and per-graph-cycle is
both honest and something a reader can picture.

Attribution matters here more than usual: 103 is the *benchmark's* node
count, while the graph animated beside it is five boxes and twelve nodes
(`g.snapshot().nodes.len()` on the example). The panel reads "fanout
benchmark · 103 nodes" and the narration says "benchmark graph", because
without that a viewer reads the figure as describing the graph on screen —
wrong by an order of magnitude.

Absolute times are machine-specific and this is a shared cloud host, which
punishes the interpreted tier hardest and so flatters the ratio. The README
says so, and says to re-run before leaning on the 49×.

48.9s.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011boKQCBTFCkTZijPdPdnL1
Every previous version of the perf claim measured something other than what
the viewer was looking at. The last one was honest about its own numbers but
measured benches/tiers.rs's `fanout` graph — 103 nodes — beside an animation
of a five-box diagram whose real graph is twelve nodes. The two before it
quoted figures rather than measuring, one of them a 1610x ratio against tokio
on a workload built to produce it.

The example now times its own replay and prints it, so the claim is about
this graph over this data:

    46 quote changes
    3.0s of market data replayed in 484.148µs — 6196× faster than real time

That line is the reason a deterministic replay is worth having, and it needs
no workload footnote, no tier caveat and no third-party comparison. It moves
to scene 4, under the terminal showing the run that produced it, and scene 3
goes back to carrying the structural point alone.

The capture treats it as a measurement rather than an anecdote: `--release`,
repeated (`REPEATS`, default 5), median recorded with the fastest and slowest
kept alongside. That mattered — a run taken while a render was finishing came
in at 0.84 ms against a 0.40 ms idle median, and a single sample would have
put whichever one happened first on screen. The speedup renders to two
significant figures, which is all the spread supports.

The pacing gate earned its keep twice more, catching the new historical line
at 18.8 CPS and again at 18.3 after a first attempt at splitting it.

Drops the now-unused bench capture, its parser and src/bench.ts.

44.9s.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011boKQCBTFCkTZijPdPdnL1
"3.0s of market data replayed in 0.43ms — 6,196× faster than real time" was
unimpressive, and for a good reason: it measured how quiet the tape was, not
how fast the engine is. Three seconds of the LOBSTER sample is 324 messages.
Replaying a sparse feed quickly is not a claim, and a multiple of real time
is not the metric anyone in this audience compares.

The replay now takes the whole file, and reports throughput:

    91997 messages, 15387 quote changes
    replayed in 193.630ms — 475117 messages/sec

A full trading hour of AAPL — every message, through a real limit order book,
15,387 quote changes out — in about a tenth of a second. That is worth
quoting; 324 messages never was.

`LIVE_SPAN_SECONDS` now bounds only the *live* feed, because a live run is
paced by the wall clock and an hour is an hour. The replay waits for nothing,
so it takes everything. The quotes the two modes share are still identical —
the live window is a strict prefix of the replay — and the capture asserts
exactly that, plus that it really is a prefix rather than the whole thing.

`MarketData` grows a `len()` so the run can report how much work it did
without the graph knowing which feed it got.

45.3s.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011boKQCBTFCkTZijPdPdnL1
@0-jake-0
0-jake-0 force-pushed the claude/wingfoil-linkedin-video-x8wm4r branch from 4314583 to f3a6324 Compare August 15, 2026 14:34
claude added 3 commits August 15, 2026 14:56
Four versions of a speed claim were built and all four were withdrawn. The
last was measured, on the example itself, at a scale worth measuring — and it
was still wrong, for a reason that rules out the category rather than the
number. Decomposing a 156 ms replay of the hour: ~56 ms is the `lobster`
order book, ~80 ms is writing 15,387 lines to stdout, ~23 ms is wingfoil. A
headline figure there is 85% a measurement of things that are not the
framework.

The confirming test came from the other side. Swapping `println!` for a 64 KB
`BufWriter` moved the "engine" number by 30% (156 ms -> 110 ms) without
touching the engine. If the sink moves your number by a third, the number was
never about the framework. For the record, `log` and `tracing` are both
*slower* than `println!` when enabled (169 ms, 174 ms) — each adds a
timestamp, level and target on top of the same syscall per record — and both
are ~free when disabled, which is the actual argument for `logged` as a tap.

That is not a wingfoil problem; it is what a realistic workload looks like.
Payload and I/O dominate, engine dispatch is the small term, and a marketing
frame cannot carry the caveats that would make a throughput figure true. The
engine's own numbers belong in benches/README.md, where the method sits next
to them. The claims the film keeps — one definition, identical quotes in
identical order, only the clock differs — are ones the capture script asserts
on every build.

Also makes the voice-swap seam real rather than described.
`scripts/import-voice.py` takes one wav per scene, measures each, copies them
into public/ and rewrites narration.json, so a human recording re-times the
film with nothing in src/ touched. Word timings are apportioned by default and
read from a `<scene>.words.json` when one exists — the shape
@remotion/install-whisper-cpp emits — so word-level karaoke is an upgrade per
scene rather than all or nothing.

43.4s.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011boKQCBTFCkTZijPdPdnL1
`--from ~/vo` was a placeholder path in the instructions, and pointing it at a
directory that does not exist gave a bare "missing hook.wav" — which reads as a
broken tool rather than "you have not recorded anything yet".

It now explains that the path is yours to create, and prints the seven lines
with the filename each belongs in. `--script` does the same on demand, so the
lines can be read off the terminal while recording rather than dug out of
script.json.

Also documents the two things that bite: wavs must be uncompressed PCM, and the
importer copies them verbatim so levelling is the recordist's job.
Recording seven separate files is a chore, and it was the only path on offer.
`split-voice.py` takes a single continuous take and cuts it on the pauses
between lines: RMS over 20ms frames, runs of speech separated by at least
--gap of near-silence, no dependencies beyond `wave`.

It will not write anything unless it finds exactly as many segments as there
are scenes, and it prints each one beside the line it will become, so a
mis-detection is caught by eye rather than by a render. Without --to it only
reports. The two knobs when the count is wrong (--gap, --floor) are named in
the failure.

Verified by round-trip: the seven synthesised wavs concatenated with 1.3s gaps
into one 48s take, split back, and imported — seven for seven, correctly
aligned.

That round trip also surfaced something worth documenting: a trimmed recording
loses the lead-in, tail and inter-sentence padding the synthesised build adds,
so the pacing gate is stricter on imported audio. `--hold` recovers some of it
but only widens a scene's *last* sentence, which is why a short opening line
still wants real word timings.
@0-jake-0
0-jake-0 marked this pull request as draft August 16, 2026 13:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants