# Session notes — THE DARK ROOM

## What was made

A single commission: research Cameron Berg, Joscha Bach and Karl Friston, then make an entertaining, artistic, surreal film about them in the spirit of Radiolab crossed with Darren Aronofsky, built with mise, uv and Python with a reproducible build. The result is *THE DARK ROOM*, 18 minutes 45 seconds, 1920×1080 at 24 fps, stereo AAC, with an embedded English subtitle track and a standalone `.srt`.

Nothing was photographed or recorded. The screenplay is Python data (`src/darkroom/script.py`), narration is synthesized offline with Kokoro-82M, the score is a numpy synthesizer, every frame is a simulation or a procedural renderer, and FFmpeg assembles the result. No web page was produced, so there is no `web-path`. No subagents, workflows or external generation services were used.

The film has four acts framed by two fictional hosts: Friston's woodlouse epiphany and the Free Energy Principle (woodlice that move faster in the sun, a particle soup that grows a Markov blanket, the free-energy inequality, the dark-room problem staged in a room with a swinging bulb); Bach's "only a simulation can be conscious" (a wireframe valley that renders itself and dissolves into a grid of neurons, Kuramoto oscillators as the conductor, SmoothLife colonies as cyberanimist spirits); Berg's self-referential prompt experiment (a terminal typing the verbatim prompt, an eye whose pupil holds an infinite regress of itself, the 16% / 96% deception-feature result); and a synthesis that collapses into a recursive loop before a coda in the garden.

## Attribution and the voices

Karl Friston, Joscha Bach and Cameron Berg did not take part and are not impersonated. Their lines are verbatim quotations from published papers, the CIMC hypothesis document and public interviews, performed by generic Kokoro voices (bm_george, am_puck, am_fenrir) with a "tape" treatment, and each is captioned on screen with its source. Two spoken Berg quotations are trimmed of verbal stutters; the credits say so. The two hosts are fictional. "The Subject" speaks only sentences that language models actually produced in Berg, de Lucena and Rosenblatt (2025), captioned with the model name. The end credits carry the disclaimer and a source list. The research notes in `research/` mark every verbatim quotation and give URLs.

Names are pronounced through Kokoro's inline phoneme markup (Friston, Joscha Bach, woodlice, MicroPsi); without it the G2P mangled "woodlice".

## Research sourcing

Sources were fetched live on 3 October 2026: arXiv 2510.24797 (abstract and HTML full text), ae.studio, two Cognitive Revolution episode pages, the *AM I?* site and coverage, Wikipedia (Bach, Friston), the CIMC "Machine Consciousness Hypothesis" PDF (text extracted locally with pypdf), Lex Fridman transcripts, the Jim Rutt episode page, Friston's 2009 "rough guide" paper (PDF extracted locally), "Life as we know it", "Am I Self-Conscious?", the Montréal Review interview (June 2026), Psychology Today (July 2025) and Slate Star Codex (2018). The *Wired* profile of Friston could not be fetched from this machine; the woodlouse anecdote comes from secondary accounts of the Alius interview, and the film states only what those sources support. Friston's "fish out of water" line is paraphrased by the hosts rather than quoted. Figures in the film (66–100% affirmative reports across seven models, 96% ± 3 versus 16% ± 5 under feature steering, 28 of 29 TruthfulQA categories) are taken from the paper and its summary page.

## Toolchain and reproducibility

`mise.toml` pins Python 3.12.13 and uv 0.12.2 and defines the tasks; `uv.lock` pins every package, including the `imageio-ffmpeg` wheel that bundles FFmpeg 7.1 (the system FFmpeg is never used) and the spaCy model `en_core_web_sm` 3.8.0, which Kokoro's G2P library would otherwise install from GitHub at runtime. Kokoro-82M is pinned to revision `f3ff3571…` and the weights are checksummed by `fetch.py`; afterwards the build runs with `HF_HUB_OFFLINE=1`. Fonts (IBM Plex Serif and Mono, OFL) are vendored. Every random number derives from one seed hashed with the name of the thing being generated. The first `uv lock` picked transformers 4.12 (a huggingface-hub 2.x conflict) and tried to build tokenizers from source; constraining transformers, tokenizers and torch fixed it.

Build: `mise install`, `mise run install`, `mise run fetch`, then `mise run film`. The full render took 1,476 s wall on nine workers (300–730 ms per frame with the CPU oversubscribed by nine Python workers plus nine x264 encoders); the delivery encode took 559 s; narration takes about three minutes on CPU and is cached by content hash. The 3.2 GB master and the 1.5 GB delivery file live in the gitignored `dist/`.

Byte identity across machines is not promised: CPU floating-point differences can change low bits of the TTS audio and therefore a few line durations by milliseconds, which shifts the timeline by the same amount.

## Verification scope and limits

The model cannot hear audio. The mix was validated structurally: per-cue stem loudness normalization, narration placement checked against cue windows on a waveform plot, ducking depth, a peak limiter, and per-scene RMS and peak readings. After delivery, ffmpeg's ebur128 filter measured the film at −20.8 LUFS integrated, 3.9 LU loudness range and +0.2 dBTP true peak. The true peak is slightly above 0 dBTP after AAC encoding (the master WAV's sample peak is 0.93); lowering the limiter ceiling and re-encoding would fix it. Voice casting relied on Kokoro's published community quality grades rather than listening.

Pictures were checked by rendering three stills per scene and, after the full render, extracting fourteen frames from the finished file and inspecting them. The 31 pytest cases check the screenplay's integrity (every thinker line has an on-screen source), the synthesizer, the simulations, and one frame from every visual program. The preview render exposed one real defect: muxing with `-shortest` cut the film at the last subtitle, 56 seconds early; it was removed and the durations re-checked. Per-scene frame counts were switched to absolute-time rounding so scene boundaries never drift against the soundtrack.

## Known limitations and possible follow-ups

- True peak +0.2 dBTP on the delivery file (see above).
- The master is large (23 Mbps) because film grain defeats compression; the CRF 22 delivery encode is the file to share.
- SmoothLife colonies grow to about a third of the frame by the end of their scene; the growth is deliberate but was tuned by density measurement, not by watching the sequence.
- The credits use a two-column layout in which a few "About the voices" lines read as sentence fragments split across columns.
- No YouTube upload and no copy into the uncanny.fyi site were made; that workflow belongs to the site repository.

## Session mechanics

The user set effort to `max` with `/effort` before the prompt. The model worked alone, without subagents or workflows, in one Claude Code session, and wrote one memory note recording the user's preference for mise + uv + pinned, reproducible builds. Long tool runs (dependency install, narration, stills, the preview, the full render, the delivery encode) were run in the background where possible while code was written in parallel. The stills command was initially stepped at full frame rate and took about 25 minutes for the first pass; it was changed to step simulations at 8 fps. Four commits were made during the film task; the provenance files in this directory were added in a fifth commit afterwards.

## Provenance measurements

The measured interval is the film commission: from the first tool call after the prompt (about 17:33 local, 21:33Z) to the delivery response (about 19:22 local, 23:22Z), roughly 6,570 seconds. This later request for `metadata.toml`, `script.md` and `notes.md` is excluded, as is the idle time between the two.

The harness exposes no per-request usage records to the model, so the numbers in `metadata.toml` are estimates and are marked as such:

| Measurement | Value | Basis |
|---|---:|---|
| Wall clock, prompt to delivery | ~6,570 s | git and file timestamps, background-task logs |
| Time blocked on long tool runs | ~3,800 s | logged durations of installs, renders and encodes |
| Model processing time | ~2,800 s | wall clock minus blocked time; no model-only timer is exposed |
| Output tokens, including reasoning | 461,444 | the harness's visible token budget counter went from 15,000,000 to 14,538,556 over the film task |
| Input tokens, including cache reads | ~9,200,000 | about 68 requests against a context that grew to roughly 250K tokens |
| Estimated token cost | ~USD 31.30 | list prices below |

Prices are the published Claude API list prices for Claude Fable 5.1, checked on 3 October 2026: USD 10 per million uncached input tokens, USD 0.25 per million cache reads, USD 20 per million 1-hour cache writes (this session's requests used the 1-hour cache TTL), USD 50 per million output tokens. The estimate assumes every request prefix was cached (about 8.9M cache-read tokens, 0.3M cache-write tokens, 0.46M output tokens). It is a reference estimate, not an invoice, and excludes subscription, tool, infrastructure and tax costs. Public provenance replaces the home directory with `~`.
