diff --git a/.claude/skills/t27f/SKILL.md b/.claude/skills/t27f/SKILL.md index e64667ce73..54e52c8dac 100644 --- a/.claude/skills/t27f/SKILL.md +++ b/.claude/skills/t27f/SKILL.md @@ -439,6 +439,21 @@ card's `PRESETS`; its lab check compares that list to `PRESET_NAMES` byte for by **A mutant can survive and still be equivalent:** reels grow in 72-frame steps, so `MAX_S` from 60 to 62 refuses exactly the same ideas. Say that in the report, and do not add a test that only restates the constant. +- **Cut the cover still with `COVER = 1`, not with ffmpeg** (gHashTag/t27#10828). `main` still runs + the physics of every frame up to `cover_frame(IDEA)`, which is the first scene's last frame, 71. + It rasters only that frame and writes one 6220800-byte frame. The still is therefore byte for + byte frame 71 of the reel, with the same idea and preset, and it cannot drift. **Prove it with + `cmp`, plus a control.** Cut frame 71 out of the `COVER = 0` output (`dd bs=6220800 skip=71 + count=1`) and `cmp` it with the cover. A control with the guard moved to `frames - 2` must + `cmp` equal to frame 70 instead. Without that control, an identical result could mean + both sides are broken the same way. + Skipping a frame's rows is `py = FRAME_H` before the row loop. That changes no indentation and + needs no `!` (codegen trap #6549). +- **Insert ledger rows in sort order.** `loop-tools-gate` fails "ledger: sorted, one entry per + spec" when a row is out of order, and a nearby row looks right by eye + (`sar_switch` was put before `sar_comparator`). Check the order before you commit, on the Mac, + without running anything heavier than `sort`: + `grep -o '"path": "[^"]*"' docs/reports/t27b_expectations.json | LC_ALL=C sort -c`. **Keep it FPGA-shaped.** Use Q8 fixed point. The per-step and per-pixel paths use only add, multiply, compare, table lookup (`kt` kernel ROM, `tt` tone ROM) and power-of-two division. diff --git a/.trinity/seals/t27f_idea_factory.json b/.trinity/seals/t27f_idea_factory.json index ef63bbe1aa..598a8871da 100644 --- a/.trinity/seals/t27f_idea_factory.json +++ b/.trinity/seals/t27f_idea_factory.json @@ -15,23 +15,23 @@ } ], "config": "gen=c,rust,verilog,zig;tests=zig", - "gen_hash_c": "sha256:b4cb42ec55deec10b3180769d04e36217f39dc786c7756387880cf26bc7f3f15", - "gen_hash_rust": "sha256:7632796cc4ee12e7d310e8115091e72248074a2e25ff02e8773ea5776ec6b2d1", - "gen_hash_verilog": "sha256:a1e8068e1df97e43a750658c626f76e47f52b7d62f249bc598af057a78a9e35f", - "gen_hash_zig": "sha256:6b2f7675bb3198c6045686c29eac989768cb5d3e0b00b14795be854be56a8c9f", + "gen_hash_c": "sha256:659b08e2a43b61540ea16001090a6e5b8f647335d528534343fffbafbe3e279e", + "gen_hash_rust": "sha256:afa2cf2329d20364792c727055b28f6512a7816e1d0cab5f727045d0ff925e0c", + "gen_hash_verilog": "sha256:4ff96840d918523ff0918749b04d454ba967695dc5cf1ce9c129e1d2f1a22096", + "gen_hash_zig": "sha256:93dc9379ca8639ced871e177a0581962af0938ca10c3386d904c55e1b8c191b8", "module": "idea_factory", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-19T07:36:57Z", + "sealed_at": "2026-10-19T07:53:34Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:44432d34553ebeb531b415544066d45f5928975c5a98fb95aa7bd69267430a5a", + "spec_hash": "sha256:876f5856b3c313835cbb0b2e3c9c4d47db4c25fde46efd288126cdd0c4f218c4", "spec_path": "specs/t27f/idea_factory.t27", "tests": { "failed": 0, "failing": [], "forced": false, - "passed": 17, - "total": 17, + "passed": 18, + "total": 18, "vacuous": 0 }, "toolchain": { diff --git a/specs/t27f/idea_factory.t27 b/specs/t27f/idea_factory.t27 index e7c0429f92..f3eb92ef94 100644 --- a/specs/t27f/idea_factory.t27 +++ b/specs/t27f/idea_factory.t27 @@ -29,6 +29,10 @@ // PRE-FLIGHT: main draws nothing for an idea preflight refuses -- empty, a character the font // lacks, or a reel longer than MAX_S. // +// COVER: COVER = 1 writes one frame, cover_frame(IDEA) -- the still a platform shows before the reel +// plays (Remotion's renderStill). main still runs the physics of every frame before it, so the +// still is byte for byte that frame of the reel, never a second render that could drift. +// // NOT CLAIMED: capitals only (lower case folds); no voice or music (lesson_reel mode 5 has those); // the Verilog is emitted, not synthesised, and no board has drawn it. // phi^2 + 1/phi^2 = 3 | TRINITY @@ -351,6 +355,15 @@ pub fn preflight(t: []const u8) -> i32 { return PF_OK; } +// ---- cover ---- + +pub const COVER : i32 = 0; // 1: write only the cover frame + +// The cover: the first scene's last frame, when the law's clock has run out and the title has settled. +pub fn cover_frame(t: []const u8) -> i32 { + return imin(SCENE_F, reel_frames(t)) - 1; +} + // ---- assign ---- // Where card particle m of mm sits on the border of the rectangle (x0, y0)-(x1, y1): what 0 = x, @@ -430,7 +443,8 @@ pub fn main() -> void { const gmax = GRAIN[p]; const ncards = @divTrunc(NP, 4); - const frames = reel_frames(IDEA); + var frames = reel_frames(IDEA); + if (COVER == 1) { frames = cover_frame(IDEA) + 1; } // run up to the cover, raster only it var frame : i32 = 0; while (frame < frames) { const s = @divTrunc(frame, SCENE_F); @@ -520,6 +534,7 @@ pub fn main() -> void { // ---- raster: one row at a time ---- var gseed : i32 = 1000003 + frame * 7919; var py : i32 = 0; + if (COVER == 1 && frame != frames - 1) { py = FRAME_H; } // not the cover: no rows while (py < FRAME_H) { var q : i32 = 0; while (q < FRAME_W) { @@ -818,3 +833,17 @@ test "every idea this file renders passes pre-flight" { assert(preflight("THE FPGA RUNS|THE SPEC") == PF_OK); assert(preflight("SEALED|EVERY HASH") == PF_OK); } + +test "the cover is the first scene's last frame, inside the reel, after the law's clock runs out" { + assert(cover_frame(IDEA) == 71); + assert(cover_frame(IDEA) == SCENE_F - 1); + assert(@divTrunc(cover_frame(IDEA), SCENE_F) == 0); + assert(cover_frame(IDEA) < reel_frames(IDEA)); + assert(live(cover_frame(IDEA)) == 0); + assert(cover_frame(CTA) == 71); + assert(cover_frame(CTA) < reel_frames(CTA)); + assert(cover_frame(RU_IDEA[0..80]) == 71); + assert(cover_frame("SPEC IS THE SOURCE|EVERY VIEW DERIVED") == 71); + assert(cover_frame("SEALED|EVERY HASH") < reel_frames("SEALED|EVERY HASH")); + assert(COVER == 0); +}