Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
1daf310
t27f: beat clock -- the reel's three beats cut where the voice change…
gHashTag Oct 18, 2026
0abe946
t27f skill: the splicer trap that kept the beat clock inside lesson_r…
gHashTag Oct 18, 2026
8aa6f91
t27f: beat graph -- the reel's three scenes crossfaded at the beat cl…
gHashTag Oct 18, 2026
20a9a6a
t27f skill: cut video on a frame and check it by counting solid-colou…
gHashTag Oct 18, 2026
0d4ad98
t27f: lay the reel's captions, cards and address over the beat video,…
gHashTag Oct 18, 2026
092db84
t27f skill: add a graph mode without moving the old ones, prove it wi…
gHashTag Oct 18, 2026
d8c4c28
skills: t27/reel-beat card -- TRI CLAW cuts a lesson reel on the beat…
gHashTag Oct 18, 2026
342733c
t27f: mode 5 overlays live inside one scene -- no caption, tag or add…
gHashTag Oct 18, 2026
4e280b2
t27f skill: keep overlays off a crossfade on whole frames, count what…
gHashTag Oct 18, 2026
919c636
Merge remote-tracking branch 'origin/master' into claude/t27f-beat-wi…
gHashTag Oct 18, 2026
14858b7
t27f: R4f -- mode 5 holds the address for REEL_CTA_MIN_MS, cut 1 capp…
gHashTag Oct 18, 2026
299fd15
Merge remote-tracking branch 'origin/master' into claude/t27f-cta-hold
gHashTag Oct 18, 2026
db36953
ledger: t27b row for specs/claw/token_shape.t27 (Refs #10633)
gHashTag Oct 18, 2026
0ce6fae
Merge origin/master into claude/t27f-cta-hold
gHashTag Oct 18, 2026
baa9732
t27f: R4e -- mode 5 cards stay inside one scene, token_floor_mux's ca…
gHashTag Oct 18, 2026
4eb371d
Merge branch 'master' of github.com:gHashTag/t27 into claude/t27f-car…
gHashTag Oct 18, 2026
ee0489b
ledger: t27b row for specs/analog/sar_comparator.t27, UNLISTED 1 -> 0…
gHashTag Oct 18, 2026
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
5 changes: 5 additions & 0 deletions .claude/skills/t27f/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,11 @@ Traps from this tick:

- Before trusting a "fast voice" grid result, work out which fixture voices are fast. `voice_room_ms(3)` is 12250 ms, so the measured lesson 1 voice (raw 12300) is already sped up. A change aimed at "fast voices" therefore changes the reel everyone actually sees.

**Letting a caller reach a printer in another spec (t27#10628).** When an overlay's `enable=` is printed in an imported spec, the importing spec cannot clip it. Split the printer into a form that takes the window (`print_alpha(lo, hi)`, `print_rise_from(o, lo, dy)` in token_floor_mux) and the old form as a thin wrapper that passes the spec's own window (`print_fade(c)`, `print_rise(o, c, dy)`). Then run `cmp` on the imported spec's own graph: it must be byte-identical (token_floor_mux: 28336 bytes, 0 differences). Run `cmp` on every mode of the importer that does not use the window too (modes 3 and 4: 0 of 162 graphs differ).

- Move the ramp along with the window. A card's rise and fade-in start at its first kept frame (`beat_first_lo`), and its fade-out ends at its last kept frame (`beat_last_hi`). If you clip only `enable=`, the card pops in fully risen on the close beat's first frame, because the ramp started on frame 392, under the crossfade.
- A window can end past the reel. `CARD_ON` gives the last card frames 392..450, but the reel's last frame is 449. Clamp the window (`card_hi`) and assert the clamp in a test.

## 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
18 changes: 9 additions & 9 deletions .trinity/seals/t27f_lesson_reel.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,31 +23,31 @@
},
{
"spec": "specs/t27f/token_floor_mux.t27",
"spec_hash": "sha256:55c02c60e581640d2f3d6db3d9ba2cdfb21d74b172290b386023e8f264e59a04"
"spec_hash": "sha256:36088094a533d70fcb7e995c53ce129b21367394057c4407f400b9dff9be6d6f"
},
{
"spec": "specs/t27f/token_floor_scenes.t27",
"spec_hash": "sha256:948cff4bd34341b40b43c8a170e2a37e3bca59f8d6bc8e35d7e5ee43fe89a4ea"
}
],
"config": "gen=c,rust,verilog,zig;tests=zig",
"gen_hash_c": "sha256:97c582c40a1e76a50e35c68414ce2653739784f9b0e3b58006deede027640fb3",
"gen_hash_rust": "sha256:f3b086a0827512fdebe957c9bc73b30592914373a1dd9137e57efb851ebf720f",
"gen_hash_verilog": "sha256:cc184e56898034ed556e2e7c9d111c028f56d60bb6adb21952a6b105d9baa0c9",
"gen_hash_zig": "sha256:aa16bbf44971ec5a6eacbd80389bca963b739a76f888cf401df14cb51abea29d",
"gen_hash_c": "sha256:5b85cb24996a66cf14d5816dfc07ffda9653e1acd9922c2b9f73526b5c37c626",
"gen_hash_rust": "sha256:b9edb32d279aaeef4388bc6c5e765676c9d86d86a434fbe019aab7c2ebc38d8e",
"gen_hash_verilog": "sha256:1847abc2cfd9f4c7c65492b702f962329e92c0477455360673e15be927c1ab5a",
"gen_hash_zig": "sha256:8a4ce7a647d698dc6d5e0f0f4999e8a523dbcf599597b89b17988f36b88f7508",
"module": "lesson_reel",
"ring": 12,
"seal_schema": 2,
"sealed_at": "2026-10-18T20:05:15Z",
"sealed_at": "2026-10-18T20:27:01Z",
"sealed_by": "t27c-bootstrap@0.5.2",
"spec_hash": "sha256:a380a3fec6de690924f36c45f40ba7aac1e002f4482fdb23d71e472357b16f19",
"spec_hash": "sha256:6097e5dd188754d6c72205a95322c542ab6c3304b1d4259d22dc9a2f5af92862",
"spec_path": "specs/t27f/lesson_reel.t27",
"tests": {
"failed": 0,
"failing": [],
"forced": false,
"passed": 27,
"total": 27,
"passed": 29,
"total": 29,
"vacuous": 0
},
"toolchain": {
Expand Down
16 changes: 8 additions & 8 deletions .trinity/seals/t27f_token_floor_mux.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"built_by": "t27c-bootstrap@0.5.2+5e4d3b998",
"built_by": "t27c-bootstrap@0.5.2+9448ab4be",
"closure": [
{
"spec": "specs/automation/owner_voice_reel.t27",
Expand Down Expand Up @@ -27,16 +27,16 @@
}
],
"config": "gen=c,rust,verilog,zig;tests=zig",
"gen_hash_c": "sha256:18710ba70409c54ce28a6baa8da85579904bf6eeec5e91f4913496216a7fb9cb",
"gen_hash_rust": "sha256:c7240d2db316d11bbff444fed4725b84a77bd566a92e6286dcc4966ae9f2bf06",
"gen_hash_verilog": "sha256:7c6af1b05a4ecfb5560ff39e4728fd96b7ab95c825c428289ab330456bc44dd7",
"gen_hash_zig": "sha256:bcd5f63e11e3fe7de9b9e2dbbe844987430961ac73d8048404c8c42e0c9d6e1c",
"gen_hash_c": "sha256:d4a9f6f33414d4a5e9f62de06f0e27f86ea182916737d818df43995b3fdfe306",
"gen_hash_rust": "sha256:b5bdd6788752a87454d7e36271b94ac41884d08cd230a6256e51fb3c00f2698f",
"gen_hash_verilog": "sha256:3b54ee703bf10b10410b6380c589dc798eadfb590b49be296353c70a2817ccb8",
"gen_hash_zig": "sha256:320cdac0773bf3a9fe387107c00513f82ab6aaa40c8d08dfef746d7bd6d84c80",
"module": "token_floor_mux",
"ring": 12,
"seal_schema": 2,
"sealed_at": "2026-10-18T15:24:22Z",
"sealed_at": "2026-10-18T20:26:58Z",
"sealed_by": "t27c-bootstrap@0.5.2",
"spec_hash": "sha256:55c02c60e581640d2f3d6db3d9ba2cdfb21d74b172290b386023e8f264e59a04",
"spec_hash": "sha256:36088094a533d70fcb7e995c53ce129b21367394057c4407f400b9dff9be6d6f",
"spec_path": "specs/t27f/token_floor_mux.t27",
"tests": {
"failed": 0,
Expand All @@ -47,7 +47,7 @@
"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 @@ -48,6 +48,7 @@
{"path": "specs/analog/linear_zoh.t27", "t27b": "blocked", "blocker": "ExprCall(@sin)", "reason": "unimplemented"},
{"path": "specs/analog/mos_table.t27", "t27b": "pass"},
{"path": "specs/analog/sar_cdac.t27", "t27b": "pass"},
{"path": "specs/analog/sar_comparator.t27", "t27b": "pass"},
{"path": "specs/api/c_abi.t27", "t27b": "pass"},
{"path": "specs/api/tri_api_context.t27", "t27b": "pass"},
{"path": "specs/api/tri_api_loop.t27", "t27b": "pass"},
Expand Down
171 changes: 157 additions & 14 deletions specs/t27f/lesson_reel.t27
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@
// 4 the beat graph (t27#10603), given LR_MS0..LR_MS2: inputs 0..2 the open, main and close
// scene clips, crossfaded on whole frames at the beat clock's cuts and trimmed to the reel
// 5 mode 3's two graphs laid over mode 4's beat video (t27#10609): inputs 0..2 the scene
// clips, 3..5 the phrases' wavs, 6 the music. The captions, tags and address are shown on
// whole frames inside the scenes and never on a crossfade (t27#10614, beat_piece_*)
// clips, 3..5 the phrases' wavs, 6 the music. The captions, tags, cards and address are shown
// on whole frames inside the scenes and never on a crossfade (t27#10614, t27#10628,
// beat_piece_*)
// Every text that comes from the lesson goes through a textfile, so no quote, colon or percent in
// a lesson can break the graph (and drawtext runs with expansion=none).
//
Expand All @@ -43,9 +44,9 @@
//
// NOT CLAIMED: the backdrop is token_floor's physics world, not a picture of the lesson; its frame
// labels are the physics reel's. Mode 5 lays the same captions and address over every scene, in
// the same places, and only their windows change with the beat. The cards are still on their
// own frames over a crossfade: token_floor_mux prints their enable= and this file does not reach
// it. Both cuts are centred on a sentence change, so a crossfade can begin under the last words
// the same places, and only their windows change with the beat. The cards keep token_floor_mux's
// windows, cut to their scene's frames as the captions are; a card with no room is dropped.
// Both cuts are centred on a sentence change, so a crossfade can begin under the last words
// of a phrase: mode 5 then drops a caption with too little room left (the cost tests). Cut 1 is
// capped at BEAT_CUT1_LAST_MS so mode 5 holds the address for REEL_CTA_MIN_MS (t27#10629): on a
// voice the reel had to speed up, lesson 1's measured voice among them, crossfade 1 then starts
Expand Down Expand Up @@ -77,6 +78,7 @@ use t27f::token_floor_scenes::TAG_X;
use t27f::token_floor_scenes::TAG_Y;
use t27f::token_floor_scenes::TAG_FONT;
use t27f::token_floor_scenes::CARDS;
use t27f::token_floor_scenes::CARD_ON;
use t27f::token_floor_scenes::CARD_LY;
use t27f::token_floor_scenes::CARD_PAD;
use t27f::token_floor_scenes::PILL_H;
Expand Down Expand Up @@ -107,6 +109,8 @@ use t27f::token_floor_mux::print_col;
use t27f::token_floor_mux::band_font;
use t27f::token_floor_mux::print_rise;
use t27f::token_floor_mux::print_fade;
use t27f::token_floor_mux::print_rise_from;
use t27f::token_floor_mux::print_alpha;
use t27f::reel_topics::TOPICS;
use t27f::reel_topics::item;
use t27f::reel_topics::id_item;
Expand Down Expand Up @@ -553,31 +557,68 @@ fn print_tag(o: usize, s: usize, dx: i32, row: usize, f: usize, lang: usize, bea
}
}

fn print_card_text(o: usize, c: usize, lang: usize, f: usize, font: i32, row: usize, dy: f64) -> void {
// Card c's window on the reel's frames: token_floor_mux's CARD_ON, its last frame inside the reel.
pub fn card_lo(c: usize) -> i32 {
return CARD_ON[2 * c];
}

pub fn card_hi(c: usize) -> i32 {
return beat_min(CARD_ON[2 * c + 1], beat_frames() - 1);
}

// A card text's rise and fade. Without beats, token_floor_mux's own (print_rise, print_fade). With
// beats the card rises from the first frame of its first kept piece, fades out on the last frame
// of its last, and is on only on its pieces (t27#10628).
fn print_card_on(o: usize, c: usize, dy: f64, beats: bool, f0: i32, f1: i32) -> void {
if (beats) {
const lo = card_lo(c);
const hi = card_hi(c);
print_rise_from(o, beat_first_lo(lo, hi, f0, f1), dy);
} else {
print_rise(o, c, dy);
}
}

fn print_card_off(c: usize, beats: bool, f0: i32, f1: i32) -> void {
if (beats) {
const lo = card_lo(c);
const hi = card_hi(c);
print_alpha(beat_first_lo(lo, hi, f0, f1), beat_last_hi(lo, hi, f0, f1));
print_pieces(lo, hi, f0, f1);
} else {
print_fade(c);
}
}

fn print_card_text(o: usize, c: usize, lang: usize, f: usize, font: i32, row: usize, dy: f64, beats: bool, f0: i32, f1: i32) -> void {
std.debug.print(",drawtext=fontfile={s}:expansion=none:textfile={s}/{s}/f{d:0>2}.txt:fontsize={d}:fontcolor=", .{ FONT, FILE_DIR, lang_dir(lang), f, font });
print_col(row);
std.debug.print(":x={d:.1}", .{card_f(o, 0) + CARD_PAD});
print_rise(o, c, dy);
print_card_on(o, c, dy, beats, f0, f1);
std.debug.print("'", .{});
print_fade(c);
print_card_off(c, beats, f0, f1);
}

fn print_card(o: usize, c: usize, t: usize, lang: usize) -> void {
print_card_text(o, c, lang, F_CARD_LABEL, label_font(o), GREY_ROW, CARD_LY[o * 3]);
// With beats a card with no kept piece is not printed, as a tag or a caption is not.
fn print_card(o: usize, c: usize, t: usize, lang: usize, beats: bool, f0: i32, f1: i32) -> void {
if (beats && beat_pieces(card_lo(c), card_hi(c), f0, f1) == 0) {
return;
}
print_card_text(o, c, lang, F_CARD_LABEL, label_font(o), GREY_ROW, CARD_LY[o * 3], beats, f0, f1);
var j : usize = 0;
while (j < 2) {
const font = code_font(o, line_len(card_code_line(t, c, j)));
var row : usize = WHITE_ROW;
if (j == 1) {
row = GOLD_ROW;
}
print_card_text(o, c, lang, F_CARD0 + 2 * c + j, font, row, CARD_LY[o * 3 + 1 + j]);
print_card_text(o, c, lang, F_CARD0 + 2 * c + j, font, row, CARD_LY[o * 3 + 1 + j], beats, f0, f1);
j = j + 1;
}
std.debug.print(",drawtext=fontfile={s}:text='PASS':fontsize={d}:fontcolor=0x000000:x={d:.1}", .{ FONT, PASS_FONT[o], pill_x(o) + PASS_DX });
print_rise(o, c, pill_dy(o) + 0.5 * PILL_H);
print_card_on(o, c, pill_dy(o) + 0.5 * PILL_H, beats, f0, f1);
std.debug.print("-text_h/2'", .{});
print_fade(c);
print_card_off(c, beats, f0, f1);
}

// The address and its page from ms a to the end. With beats they fade in from the first frame of
Expand Down Expand Up @@ -635,7 +676,7 @@ pub fn graph(o: usize, t: usize, lang: usize, m0: i32, m1: i32, m2: i32, beats:
}
var c : usize = 0;
while (c < CARDS) {
print_card(o, c, t, lang);
print_card(o, c, t, lang, beats, f0, f1);
c = c + 1;
}
const n = phrases_for(m0, m1, m2);
Expand Down Expand Up @@ -946,6 +987,19 @@ pub fn beat_first_lo(lo: i32, hi: i32, f0: i32, f1: i32) -> i32 {
return lo;
}

// The last frame of the last kept piece, where a card's fade out ends in mode 5; hi when none is
// kept.
pub fn beat_last_hi(lo: i32, hi: i32, f0: i32, f1: i32) -> i32 {
var b : usize = 3;
while (b > 0) {
b = b - 1;
if (beat_piece_kept(lo, hi, b, f0, f1)) {
return beat_piece_hi(hi, b, f0, f1);
}
}
return hi;
}

// Mode 5's enable= for overlay lo..hi: exactly its kept pieces' frames.
fn print_pieces(lo: i32, hi: i32, f0: i32, f1: i32) -> void {
std.debug.print(":enable='", .{});
Expand Down Expand Up @@ -1687,6 +1741,95 @@ test "in mode 5 no caption, tag or address is on a crossfade, every lesson, both
assert(dropped < windows);
}

test "a card's window is token_floor_mux's, inside the reel, and its last kept piece ends where it should" {
assert(card_lo(0) == 98);
assert(card_hi(0) == 176);
assert(card_lo(2) == 392);
// CARD_ON says 450, a frame past the reel's last
assert(CARD_ON[5] == beat_frames());
assert(card_hi(2) == beat_frames() - 1);
const f0 : i32 = 100;
const f1 : i32 = 300;
// one kept piece: first and last are its own ends
assert(beat_last_hi(95, 160, f0, f1) == 160);
assert(beat_first_lo(95, 160, f0, f1) == 116);
// two kept pieces: the last ends with the window, the first starts with it
assert(beat_last_hi(80, 140, f0, f1) == 140);
assert(beat_last_hi(80, 108, f0, f1) == 99);
// none kept: the window's own end
assert(beat_pieces(102, 110, f0, f1) == 0);
assert(beat_last_hi(102, 110, f0, f1) == 110);
}

test "in mode 5 no card is on a crossfade, every lesson, both languages, every voice the clock accepts" {
var voices : usize = 0;
var crossing : usize = 0;
var kept : usize = 0;
var t : usize = 0;
while (t < TOPICS) {
var lang : usize = 0;
while (lang < 2) {
var i : usize = 0;
while (i < 4) {
var j : usize = 0;
while (j < 4) {
var k : usize = 0;
while (k < 5) {
const m0 = BEAT_GRID_M0[i];
const m1 = BEAT_GRID_M1[j];
const m2 = BEAT_GRID_M2[k];
if (voice_fits(m0, m1, m2)) {
voices = voices + 1;
const f0 = beat_xfade_frame(beat_cut_ms(t, lang, m0, m1, m2, 0));
const f1 = beat_xfade_frame(beat_cut_ms(t, lang, m0, m1, m2, 1));
var c : usize = 0;
while (c < CARDS) {
const lo = card_lo(c);
const hi = card_hi(c);
assert(pieces_clear(lo, hi, f0, f1));
if (on_xfade(lo, hi, f0, f1)) {
crossing = crossing + 1;
}
if (beat_pieces(lo, hi, f0, f1) > 0) {
kept = kept + 1;
// the rise starts and the fade ends on frames the card is on
const a = beat_first_lo(lo, hi, f0, f1);
const z = beat_last_hi(lo, hi, f0, f1);
assert(a >= lo);
assert(z <= hi);
assert(z >= a);
assert(on_xfade(a, a, f0, f1) == false);
assert(on_xfade(z, z, f0, f1) == false);
}
c = c + 1;
}
// the last card is on in the close beat, which owns 45 frames
assert(beat_pieces(card_lo(2), card_hi(2), f0, f1) == 1);
}
k = k + 1;
}
j = j + 1;
}
i = i + 1;
}
lang = lang + 1;
}
t = t + 1;
}
assert(voices > 0);
assert(kept >= voices);
// control: token_floor_mux's own card windows do meet the crossfades, so the rule has work to do
assert(crossing > 0);
// on lesson 1's measured voice cut 1 is capped, crossfade 1 starts on frame 389, and mode 3's
// last card, on from frame 392, rises under it; mode 5 starts it on the close beat's first frame
const f0 = beat_xfade_frame(beat_cut_ms(0, 0, M_EN0, M_EN1, M_EN2, 0));
const f1 = beat_xfade_frame(beat_cut_ms(0, 0, M_EN0, M_EN1, M_EN2, 1));
assert(f1 == 389);
assert(on_xfade(card_lo(2), card_hi(2), f0, f1));
assert(beat_first_lo(card_lo(2), card_hi(2), f0, f1) == 405);
assert(beat_last_hi(card_lo(2), card_hi(2), f0, f1) == 449);
}

test "the cost on lesson 1's measured voice: a caption across a crossfade with no room is dropped, and every phrase keeps one" {
var dropped : usize = 0;
var t : usize = 0;
Expand Down
34 changes: 23 additions & 11 deletions specs/t27f/token_floor_mux.t27
Original file line number Diff line number Diff line change
Expand Up @@ -321,25 +321,37 @@ fn line_row(l: usize) -> usize {
return GOLD_ROW;
}

// v, the card's ramp, as an ffmpeg expression in the frame number n: the same clip as card_opacity
fn print_ramp(c: usize) -> void {
std.debug.print("clip(min((n-{d})/{d:.1},({d}-n)/{d:.1}),0,1)", .{ CARD_ON[2 * c], CARD_IN, CARD_ON[2 * c + 1] + 1, CARD_OUT });
// v, the ramp of a card up on frames lo..hi, as an ffmpeg expression in the frame number n: the
// same clip as card_opacity. A caller with its own window passes it (lesson_reel's mode 5,
// t27#10628); card c's own is CARD_ON.
fn print_ramp(lo: i32, hi: i32) -> void {
std.debug.print("clip(min((n-{d})/{d:.1},({d}-n)/{d:.1}),0,1)", .{ lo, CARD_IN, hi + 1, CARD_OUT });
}

// the text's top: its place below the card's top, plus card_drop from frame lo
pub fn print_rise_from(o: usize, lo: i32, dy: f64) -> void {
std.debug.print(":y='{d:.1}+{d:.1}*(1+{d:.1}*max(0,(n-{d})/{d}))*exp(-{d:.1}*max(0,(n-{d})/{d}))", .{ card_f(o, 1) + dy, CARD_RISE, CARD_W, lo, FPS, CARD_W, lo, FPS });
}

// the text's top: its place below the card's top, plus card_drop
pub fn print_rise(o: usize, c: usize, dy: f64) -> void {
std.debug.print(":y='{d:.1}+{d:.1}*(1+{d:.1}*max(0,(n-{d})/{d}))*exp(-{d:.1}*max(0,(n-{d})/{d}))", .{ card_f(o, 1) + dy, CARD_RISE, CARD_W, CARD_ON[2 * c], FPS, CARD_W, CARD_ON[2 * c], FPS });
print_rise_from(o, CARD_ON[2 * c], dy);
}

// the smootherstep of the ramp, as card_opacity has it, and the frames the card is up
pub fn print_fade(c: usize) -> void {
// the smootherstep of the ramp over lo..hi, as card_opacity has it, without an enable=
pub fn print_alpha(lo: i32, hi: i32) -> void {
std.debug.print(":alpha='pow(", .{});
print_ramp(c);
print_ramp(lo, hi);
std.debug.print(",3)*(", .{});
print_ramp(c);
print_ramp(lo, hi);
std.debug.print("*(6*", .{});
print_ramp(c);
std.debug.print("-15)+10)':enable='between(n,{d},{d})'", .{ CARD_ON[2 * c], CARD_ON[2 * c + 1] });
print_ramp(lo, hi);
std.debug.print("-15)+10)'", .{});
}

// card c's own fade and the frames it is up
pub fn print_fade(c: usize) -> void {
print_alpha(CARD_ON[2 * c], CARD_ON[2 * c + 1]);
std.debug.print(":enable='between(n,{d},{d})'", .{ CARD_ON[2 * c], CARD_ON[2 * c + 1] });
}

fn print_card(o: usize, c: usize) -> void {
Expand Down
Loading