Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions .claude/skills/t27f/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,50 @@ grep `specs/` for every name it would import. If any of them collides, put the n
spec that owns those names, under a prefix such as `beat_`. The beat clock (t27#10596, PR #10598)
lives in `lesson_reel.t27` for this reason.

**Cutting video at a ms time: land on a frame, then measure the runs (t27#10603, PR #10605).**

ffmpeg `xfade` starts on the first frame whose time is at or after `offset`. This is how to get the cut you planned:

- Floor the cut to a base frame: `f = (c - half) * fps / 1000`.
- Print the offset as `trunc(f * 1000 / fps)` ms. Rounding up misses frame f on 2 frames in 3.
- Put every input through `fps=30,settb=AVTB` so the offset is not rounded again.
- Chained `xfade` offsets are absolute reel times when each clip starts at its own frame 0.

To check a graph without looking at pictures:

1. Feed it solid `color=` inputs at `s=2x2`.
2. Write `-f rawvideo -pix_fmt rgb24`.
3. Count the runs with `od -An -tu1 -w12 -v | cut -c1-12 | uniq -c`.

The runs are the scene lengths and must equal the spec's frame counts. Expect one extra frame when the offset falls on a whole ms.

Lesson main functions read `std.c.getenv`, so build them on the lab with `zig build-exe -lc`.

**Adding a mode to a graph printer without moving the old ones (t27#10609, PR #10610).**

- Pass the old callers a flag (`graph(..., false)`) instead of copying the printer.
- Give the input numbers a function with a test that every input is used exactly once, in each layout.
- Prove the old output did not move: on the lab, build the old spec (from `git show <ref>:path`) and the new one, run both on a grid of lessons, languages and voices, and `cmp -s` every graph. PR #10610 compared 96 graphs and 0 differed.
- For a large frame, render lossless (`-c:v libx264 -qp 0`) and crop a 2x2 pixel outside every text box (`crop=2:2:x:y,format=rgb24`). The solid runs must still equal the spec's frame counts.

Traps from this tick:

- An untyped array literal in a test (`var a = [_]usize{...}`) becomes a Zig tuple, which cannot be indexed at runtime ("tuple field index must be comptime-known"). Declare `var a : [8]usize = undefined;` and fill it in a loop.
- `fs-lab.sh "echo $b64a ... ; echo $b64b ..."` with two large files exits 0 and writes nothing. Upload one file per call and compare md5 on both sides.
- zsh does not word-split `set -- $p`. Write one call per file.

**Keeping overlays off a crossfade (t27#10614, PR #10627).**

- Work in whole frames, not seconds. Give each scene a function for the frames it owns outside its crossfades, clip each overlay to it, and print exactly those frames as `between(n,lo,hi)` terms joined with `+`. A test checks that the printed frames equal the frames `between(t,a,b)` showed before.
- Drop a piece that has no room; never squeeze it. Name the cost in a test: count what is dropped, assert that each drop is on a crossfade, and assert that every phrase keeps at least one caption.
- To check the pixels, render lossless from solid-colour clips. Crop a full-width band 40 rows tall over the text, split the raw file per frame, and count the distinct colours per frame with `od -An -tu1 -w3 -v | sort -u | wc -l`. Crossfade frames must show 1. The frames with text are the control: they must be many.

Traps from this tick:

- A minimum-length rule silently drops spoken captions. A short chunk that straddles a crossfade leaves two pieces under the minimum, and the voice speaks words that are never shown. A grid test that says "no overlay on a crossfade" stays green while this happens. Only a count of what was dropped finds it.
- Lab scripts run under `sh`, which is dash: `$((10#$x))` fails with "expecting EOF". Strip leading zeros with `sed 's/^0*//'` and default with `${i:-0}`.
- The Mac hook reads the whole Bash command. A heredoc PR body that names the report command gets refused. Write the body with the Write tool and pass `--body-file`.

## Show a spec (the core: exporter for what it reads, by hand for the rest)

1. Load the spec: `mcp__t27__t27_spec` with `path` and `source: true`, or the file from a
Expand Down
28 changes: 28 additions & 0 deletions .trinity/seals/skills_skill_t27_reel_beat.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"built_by": "t27c-bootstrap@0.5.2+9448ab4be",
"closure": [],
"config": "gen=c,rust,verilog,zig;tests=zig",
"gen_hash_c": "sha256:5642bc71a3f58fb401c34d2e9260f2190b2335994b58272bfd47bdb2e50cca07",
"gen_hash_rust": "sha256:706879bc5d6542378435b0982dbb44e52cc1b9b85b34d7f06149ec7668b5fdd7",
"gen_hash_verilog": "sha256:596ced743ed9651f967ac93bae97d97f44b882ea6ed659d4ebc6ec67a16829b8",
"gen_hash_zig": "sha256:24befaf4ec44f81b268d7a1f58e3e7312c16da94c50b0442ac76e23e5ea1440a",
"module": "skill_t27_reel_beat",
"ring": 12,
"seal_schema": 2,
"sealed_at": "2026-10-18T19:16:57Z",
"sealed_by": "t27c-bootstrap@0.5.2",
"spec_hash": "sha256:b609e2d632860b22d61a44f12e3d8e3bf11663d080dada9cec88c19ac9e5becd",
"spec_path": "specs/skills/t27-reel-beat.t27",
"tests": {
"failed": 0,
"failing": [],
"forced": false,
"passed": 1,
"total": 1,
"vacuous": 0
},
"toolchain": {
"test_runner": "sha256:8512b80da533b79289110fca4a65f3336abd7a059ca4b72ea595d5e33ff8ad10",
"zig": "0.16.0"
}
}
18 changes: 9 additions & 9 deletions .trinity/seals/t27f_lesson_reel.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,27 +31,27 @@
}
],
"config": "gen=c,rust,verilog,zig;tests=zig",
"gen_hash_c": "sha256:6a381e722c19647a8f05c723633503d1d931b143538c6183f8832fec577ebeb7",
"gen_hash_rust": "sha256:bf7b0432da8ec2e1bcf0f139aa271e9df74986d67c81dc1bb0bcacd19357d1db",
"gen_hash_verilog": "sha256:836331bc2320ec6d80ce1580e30f1d3a9cc9fbd89325995f141c090282a68ec7",
"gen_hash_zig": "sha256:50a202697595b03abac33b3707f25a239343ae7aa2256b0c43fec31b950cf61b",
"gen_hash_c": "sha256:30f4f017bc4dd4c32c1ec195d83f827f10e9d260f30056ebd6f444cf034ca3ea",
"gen_hash_rust": "sha256:cd6858c82060106802b3cced271e84811d8ee02b8c4f2ed18ba75cf8068861eb",
"gen_hash_verilog": "sha256:d9d8bbee11770df310cf863654c914b318ea03940780b5a9e116cec3f4e33991",
"gen_hash_zig": "sha256:f5762a5368a29d6b2978d7ea365deed7c9c7a0d4994814a3d6586c039fdcbf25",
"module": "lesson_reel",
"ring": 12,
"seal_schema": 2,
"sealed_at": "2026-10-18T18:33:33Z",
"sealed_at": "2026-10-18T19:36:34Z",
"sealed_by": "t27c-bootstrap@0.5.2",
"spec_hash": "sha256:87165b892aaaba0647fe1657669524eca65c31547f11f4e4994abb830c673b0e",
"spec_hash": "sha256:b1ca187bbf04f6595be7dbf25b6a656286b07a86c50def9a0605c9ec56f017ec",
"spec_path": "specs/t27f/lesson_reel.t27",
"tests": {
"failed": 0,
"failing": [],
"forced": false,
"passed": 13,
"total": 13,
"passed": 25,
"total": 25,
"vacuous": 0
},
"toolchain": {
"test_runner": "sha256:1982ae948e24d00c5d221ccec12dfce1eb01204b53e37b0f857f93b16d64629d",
"test_runner": "sha256:8512b80da533b79289110fca4a65f3336abd7a059ca4b72ea595d5e33ff8ad10",
"zig": "0.16.0"
}
}
1 change: 1 addition & 0 deletions docs/reports/t27b_expectations.json
Original file line number Diff line number Diff line change
Expand Up @@ -1407,6 +1407,7 @@
{"path": "specs/skills/t27-measure-corpus.t27", "t27b": "pass"},
{"path": "specs/skills/t27-phi-loop.t27", "t27b": "pass"},
{"path": "specs/skills/t27-providers-steward.t27", "t27b": "pass"},
{"path": "specs/skills/t27-reel-beat.t27", "t27b": "pass"},
{"path": "specs/skills/t27-self-host.t27", "t27b": "pass"},
{"path": "specs/skills/t27-t27f.t27", "t27b": "pass"},
{"path": "specs/skills/t27-tri-pipeline.t27", "t27b": "pass"},
Expand Down
48 changes: 48 additions & 0 deletions specs/skills/t27-reel-beat.t27
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// SPDX-License-Identifier: Apache-2.0
; specs/skills/t27-reel-beat.t27 -- skill t27/reel-beat
; Source of truth for the skill card on t27.ai (#/skills?skill=t27/reel-beat).
; How TRI CLAW cuts a lesson reel on the beat: LR_MODE=4 crossfades the open, main and close
; scene clips at the beat clock's cuts, on whole frames, and LR_MODE=5 lays mode 3's captions,
; cards and address over that video. Mode 5's inputs are 0..2 the scene clips, 3..5 the
; phrases' wavs and 6 the music. The law lives once, in specs/t27f/lesson_reel.t27 (beat_*
; functions and their tests); this card points at it and copies none. The scene clips are
; inputs: lesson_reel does not read the content factory's scene plan yet. There is no
; SKILL.md behind it yet, so the card is spec-only (see "Witness labels" in
; specs/skills/README.md). Issues gHashTag/t27#10603 and
; gHashTag/t27#10609, epic gHashTag/t27#10560.
; The JSON the site serves is generated from this file by the real compiler;
; nothing below is parsed with a regex.
; phi^2 + 1/phi^2 = 3 | TRINITY

module skill_t27_reel_beat;

pub const KIND : str = "skill";
pub const ID : str = "t27/reel-beat";
pub const NAME : str = "Reel Beat";
pub const REPO : str = "t27";
; No SKILL.md exists for this card.
pub const SOURCE : str = "";
pub const SUMMARY_EN : str = "Cut a 15 second lesson reel on the beat clock: crossfade the open, main and close scenes at whole-frame cuts that never fall inside a spoken phrase, lay the captions and address on top, and refuse a voice that does not fit";
pub const COMMAND : str = "/reel-beat";
; Spec this skill stands on: the beat clock and the graph printer.
pub const SPECS : [1]str = ["specs/t27f/lesson_reel.t27"];
pub const TAGS : [4]str = ["social-visual", "reel", "beat", "t27f"];
pub const ENABLED : bool = true;
; Default budget; no SKILL.md declares a timeout.
pub const TIMEOUT_MIN : u16 = 30;

test basic_constants {
assert KIND == "skill";
assert ID == "t27/reel-beat";
assert NAME == "Reel Beat";
assert REPO == "t27";
assert SOURCE == "";
assert COMMAND == "/reel-beat";
assert ENABLED == true;
assert TIMEOUT_MIN == 30;
assert TAGS.len == 4;
assert TAGS[0] == "social-visual";
assert TAGS[2] == "beat";
assert SPECS.len == 1;
assert SPECS[0] == "specs/t27f/lesson_reel.t27";
}
Loading
Loading