From d21f483a19b1a7d1c6456a3d27151c70331d6c54 Mon Sep 17 00:00:00 2001 From: Dmitrii Vasilev Date: Mon, 19 Oct 2026 09:26:34 +0700 Subject: [PATCH 01/21] t27f: idea factory -- any idea in any style preset, every element a particle (Closes #10771) particle_ds.t27 is the particle design system: five presets (noir-gold, neon-vortex, trit-charge, paper-ink, rain) as rows of integers -- palette, blend, radius, gain, spring, dashpot, force law, kick, card -- and one Q8 integer step, kernel, tone curve and finish. idea_factory.t27 is the factory: (idea, preset) -> 1080x1920 RGB24 frames. It wraps the idea into lines and scenes, sets each scene in the 3x5 font, gives every particle a target cell or a card-border point at each cut, and closes on the address. Lab: test-report 12/12 and 8/8 pass, 0 vacuous; seals saved and verified; dupe_scan clean; 5-preset preview rendered at 1080x1920. Co-Authored-By: Claude Opus 5.5 --- .trinity/seals/t27f_idea_factory.json | 37 ++ .trinity/seals/t27f_particle_ds.json | 33 ++ specs/t27f/idea_factory.t27 | 564 ++++++++++++++++++++++++++ specs/t27f/particle_ds.t27 | 419 +++++++++++++++++++ 4 files changed, 1053 insertions(+) create mode 100644 .trinity/seals/t27f_idea_factory.json create mode 100644 .trinity/seals/t27f_particle_ds.json create mode 100644 specs/t27f/idea_factory.t27 create mode 100644 specs/t27f/particle_ds.t27 diff --git a/.trinity/seals/t27f_idea_factory.json b/.trinity/seals/t27f_idea_factory.json new file mode 100644 index 0000000000..fff853f045 --- /dev/null +++ b/.trinity/seals/t27f_idea_factory.json @@ -0,0 +1,37 @@ +{ + "built_by": "t27c-bootstrap@0.5.2+355d44a39", + "closure": [ + { + "spec": "specs/t27f/particle_ds.t27", + "spec_hash": "sha256:00e84a7492536bf54ac69aa0cd852dea4727477acee12538f9c4b145c3d9c8f3" + }, + { + "spec": "specs/t27f/scene_raster.t27", + "spec_hash": "sha256:7be7118d865aa65fd1ccb989ab160df2feb46c9ded0a15ca6510b747301ee4a6" + } + ], + "config": "gen=c,rust,verilog,zig;tests=zig", + "gen_hash_c": "sha256:49e275c87108c8621d521226b89afb5f6c6a39f74de8e8bba502fbd1e39ad843", + "gen_hash_rust": "sha256:7d03e6e99a15344a1c8272b7f96ffc8ddcbfdb1cf44ab7f70f7a0e37847529bc", + "gen_hash_verilog": "sha256:0f761cb6e48d10ac555e51b8e5722eb8b02074a87ca2055aaec9241754140c49", + "gen_hash_zig": "sha256:0730d911d7047dc707075d16c8ff04afe222f4422a24344eff10b90e72edfdf0", + "module": "idea_factory", + "ring": 12, + "seal_schema": 2, + "sealed_at": "2026-10-19T02:25:39Z", + "sealed_by": "t27c-bootstrap@0.5.2", + "spec_hash": "sha256:34f59b06d780be9cbbae1b0577e7c96ed5b6b7d2451e61ada1b1dde2fd58b6b8", + "spec_path": "specs/t27f/idea_factory.t27", + "tests": { + "failed": 0, + "failing": [], + "forced": false, + "passed": 8, + "total": 8, + "vacuous": 0 + }, + "toolchain": { + "test_runner": "sha256:8512b80da533b79289110fca4a65f3336abd7a059ca4b72ea595d5e33ff8ad10", + "zig": "0.16.0" + } +} \ No newline at end of file diff --git a/.trinity/seals/t27f_particle_ds.json b/.trinity/seals/t27f_particle_ds.json new file mode 100644 index 0000000000..5797784dfd --- /dev/null +++ b/.trinity/seals/t27f_particle_ds.json @@ -0,0 +1,33 @@ +{ + "built_by": "t27c-bootstrap@0.5.2+355d44a39", + "closure": [ + { + "spec": "specs/t27f/scene_raster.t27", + "spec_hash": "sha256:7be7118d865aa65fd1ccb989ab160df2feb46c9ded0a15ca6510b747301ee4a6" + } + ], + "config": "gen=c,rust,verilog,zig;tests=zig", + "gen_hash_c": "sha256:03823e75050d119fede755d7e3b218cd924f47ad3d310bcc39f5a00ea315a715", + "gen_hash_rust": "sha256:33f633fdef8835e3d69e6e758e6bb180de9e103fca81005405999c391bec82f4", + "gen_hash_verilog": "sha256:c5ef23af6415734e2f11a6dd645de11cd86a4f040c331595fa3f11c4765a71db", + "gen_hash_zig": "sha256:262eb3241c84d2de64e67b59ea700299f8cf513d1d3a3e2ecf25279f5421f63f", + "module": "particle_ds", + "ring": 12, + "seal_schema": 2, + "sealed_at": "2026-10-19T02:25:36Z", + "sealed_by": "t27c-bootstrap@0.5.2", + "spec_hash": "sha256:00e84a7492536bf54ac69aa0cd852dea4727477acee12538f9c4b145c3d9c8f3", + "spec_path": "specs/t27f/particle_ds.t27", + "tests": { + "failed": 0, + "failing": [], + "forced": false, + "passed": 12, + "total": 12, + "vacuous": 0 + }, + "toolchain": { + "test_runner": "sha256:8512b80da533b79289110fca4a65f3336abd7a059ca4b72ea595d5e33ff8ad10", + "zig": "0.16.0" + } +} \ No newline at end of file diff --git a/specs/t27f/idea_factory.t27 b/specs/t27f/idea_factory.t27 new file mode 100644 index 0000000000..8affe44e9f --- /dev/null +++ b/specs/t27f/idea_factory.t27 @@ -0,0 +1,564 @@ +// SPDX-License-Identifier: Apache-2.0 +// specs/t27f/idea_factory.t27 -- the content factory: (idea, preset) -> frames. Any idea, in any +// style of the particle design system, and every element on screen is a particle. Issue +// gHashTag/t27#10771, epic #10560. +// +// WHY: the owner, 2026-10-19: one factory, on the factory pattern, that unpacks any idea in any +// style preset, with the factory and all its elements formed by particle physics -- the visual +// design system of the OS, so it has to be able to run on the FPGA too. Before this, every reel +// was a hand-built module with its own f64 constants (token_floor, reel_particles, lesson_reel). +// +// THE FACTORY, in four stages, each a pure function the tests pin: +// wrap the idea is cut into lines of at most MAXC characters (greedy, by words; a longer +// word is split) and lines into scenes of at most MAXL; an explicit `|` cuts a scene. +// layout a scene's lines are set in the 3x5 font of scene_raster, centred, at the largest cell +// size S that fits the text box; every lit font pixel is a cell, a target for particles. +// assign at each scene cut every particle gets a new target: a sub-slot of a cell, or -- when +// the preset has a card -- a point on the card's border around the text. The closing +// scene is the address, CTA. +// render particle_ds moves every particle (spring, dashpot, the preset's law, the cut's kick) +// and paints it as a soft disc; main writes 1080x1920 RGB24 frames to stderr for ffmpeg. +// The preset is a number. Nothing in this file names a colour, a law or a speed. +// +// THE NUMBERS: layout and assignment run once per scene cut and may divide by a variable (the cell +// size); the per-step and per-pixel paths are particle_ds's -- integer, Q8, table lookups. +// +// NOT CLAIMED: the font is ASCII (no Cyrillic); no voice or music (lesson_reel mode 5 has those); +// gen-verilog has not been run on this module and no board has drawn it. +// phi^2 + 1/phi^2 = 3 | TRINITY + +module idea_factory; + +use t27f::scene_raster::glyph_on; +use t27f::scene_raster::rnd; +use t27f::scene_raster::lcg; +use t27f::scene_raster::clamp; +use t27f::scene_raster::imin; +use t27f::scene_raster::imax; +use t27f::scene_raster::iabs; +use t27f::scene_raster::fmod; +use t27f::scene_raster::red; +use t27f::scene_raster::green; +use t27f::scene_raster::blue; +use t27f::particle_ds::ONE; +use t27f::particle_ds::BG; +use t27f::particle_ds::KEY_COL; +use t27f::particle_ds::GRAIN; +use t27f::particle_ds::CARD; +use t27f::particle_ds::TONE_N; +use t27f::particle_ds::live; +use t27f::particle_ds::law_ax; +use t27f::particle_ds::law_ay; +use t27f::particle_ds::step_v; +use t27f::particle_ds::kick_x; +use t27f::particle_ds::kick_y; +use t27f::particle_ds::start_x; +use t27f::particle_ds::start_y; +use t27f::particle_ds::start_vx; +use t27f::particle_ds::start_vy; +use t27f::particle_ds::particle_col; +use t27f::particle_ds::channel_of; +use t27f::particle_ds::reach; +use t27f::particle_ds::kern; +use t27f::particle_ds::tone; +use t27f::particle_ds::vignette; +use t27f::particle_ds::key_lift; +use t27f::particle_ds::finish; + +pub const ID : str = "idea-factory"; +pub const VERSION : u8 = 1; + +// ---- the factory's input: the lab recipe rewrites these two lines per render ---- +pub const IDEA : str = "PARTICLES BUILD EVERY FRAME|ONE SPEC MANY STYLES"; +pub const PRESET : usize = 0; +pub const CTA : str = "T27.AI"; + +// ---- the frame ---- +pub const FRAME_W : i32 = 1080; +pub const FRAME_H : i32 = 1920; +pub const FPS : i32 = 30; +pub const SCENE_F : i32 = 72; // 2.4 s a scene: the law's clock runs out at frame 43 +pub const STEPS : i32 = 2; // integrator steps a frame +pub const NP : i32 = 3072; // particles; main's arrays are this long +pub const CX : i32 = 540; // the text block's centre +pub const CY : i32 = 864; // 0.45 of the height, under the key light + +// ---- the wrap and the layout ---- +pub const MAXC : i32 = 9; // characters a line +pub const MAXL : i32 = 4; // lines a scene +pub const LINE_PITCH : i32 = 7; // font pixels from one line's top to the next's +pub const TEXT_W : i32 = 907; // 0.84 of the width +pub const TEXT_H : i32 = 768; // 0.40 of the height +pub const CELL_MAX : i32 = 40; // the largest font pixel, px +pub const MAXCELLS : i32 = 1024; // MAXL * MAXC * 15 = 540 is the most a scene can light + +// ---- wrap ---- + +// The k-th line of text t: what 0 = its first byte, 1 = its length in bytes, 2 = its scene; -1 when +// there is no such line. what 3 = the number of lines, what 4 = the number of scenes (k unused). +pub fn wrap_walk(t: []const u8, k: i32, what: i32) -> i32 { + const n = @as(i32, @intCast(t.len)); + var line : i32 = 0; + var scene : i32 = 0; + var in_scene : i32 = 0; + var ls : i32 = 0 - 1; + var le : i32 = 0; + var i : i32 = 0; + var done : bool = false; + while (!done) { + var flush : bool = false; + var cut : bool = false; + if (i >= n) { + flush = true; + done = true; + } else { + const c = @as(i32, t[@as(usize, @intCast(i))]); + if (c == 32) { + i = i + 1; + } else if (c == 124) { + flush = true; + cut = true; + i = i + 1; + } else { + var j : i32 = i; + while (j < n && j - i < MAXC && t[@as(usize, @intCast(j))] != 32 && t[@as(usize, @intCast(j))] != 124) { + j = j + 1; + } + if (ls >= 0 && j - ls > MAXC) { + flush = true; + } else { + if (ls < 0) { ls = i; } + le = j; + i = j; + } + } + } + if (flush && ls >= 0) { + if (line == k) { + if (what == 0) { return ls; } + if (what == 1) { return le - ls; } + if (what == 2) { return scene; } + } + line = line + 1; + in_scene = in_scene + 1; + ls = 0 - 1; + if (in_scene == MAXL) { + scene = scene + 1; + in_scene = 0; + } + } + if (cut && in_scene > 0) { + scene = scene + 1; + in_scene = 0; + } + } + if (what == 3) { return line; } + if (what == 4) { + if (in_scene > 0) { return scene + 1; } + return scene; + } + return 0 - 1; +} +pub fn line_count(t: []const u8) -> i32 { + return wrap_walk(t, 0, 3); +} +pub fn scene_count(t: []const u8) -> i32 { + return wrap_walk(t, 0, 4); +} + +// ---- layout ---- + +// Scene s of text t, set in the font: what 2 = the number of lit cells; 0, 1 = the c-th cell's +// centre x, y in px; 3, 4, 5, 6 = the text block's left, top, right, bottom; 7 = the cell size S. +// An empty scene has no cells and a zero block. +pub fn scene_cell(t: []const u8, s: i32, c: i32, what: i32) -> i32 { + const lines = line_count(t); + var nl : i32 = 0; + var maxlen : i32 = 0; + var first : i32 = 0 - 1; + var k : i32 = 0; + while (k < lines) { + if (wrap_walk(t, k, 2) == s) { + if (first < 0) { first = k; } + nl = nl + 1; + maxlen = imax(maxlen, wrap_walk(t, k, 1)); + } + k = k + 1; + } + if (nl == 0) { return 0; } + const cols = maxlen * 4 - 1; + const rows = nl * LINE_PITCH - 2; + const cs = imin(imin(@divTrunc(TEXT_W, cols), @divTrunc(TEXT_H, rows)), CELL_MAX); + const ox = CX - @divTrunc(cols * cs, 2); + const oy = CY - @divTrunc(rows * cs, 2); + if (what == 3) { return ox; } + if (what == 4) { return oy; } + if (what == 5) { return ox + cols * cs; } + if (what == 6) { return oy + rows * cs; } + if (what == 7) { return cs; } + var n : i32 = 0; + var li : i32 = 0; + while (li < nl) { + const st = wrap_walk(t, first + li, 0); + const ln = wrap_walk(t, first + li, 1); + const lox = CX - @divTrunc((ln * 4 - 1) * cs, 2); + var ci : i32 = 0; + while (ci < ln) { + const ch = @as(i32, t[@as(usize, @intCast(st + ci))]); + var gy : i32 = 0; + while (gy < 5) { + var gx : i32 = 0; + while (gx < 3) { + if (glyph_on(ch, gx, gy)) { + if (n == c) { + if (what == 0) { return lox + (ci * 4 + gx) * cs + @divTrunc(cs, 2); } + if (what == 1) { return oy + (li * LINE_PITCH + gy) * cs + @divTrunc(cs, 2); } + } + n = n + 1; + } + gx = gx + 1; + } + gy = gy + 1; + } + ci = ci + 1; + } + li = li + 1; + } + if (what == 2) { return n; } + return 0 - 1; +} + +// The reel's scene s: the idea's scenes, then the address. +pub fn reel_scenes(t: []const u8) -> i32 { + return scene_count(t) + 1; +} +pub fn reel_cell(t: []const u8, s: i32, c: i32, what: i32) -> i32 { + if (s < scene_count(t)) { return scene_cell(t, s, c, what); } + return scene_cell(CTA, 0, c, what); +} +pub fn reel_frames(t: []const u8) -> i32 { + return reel_scenes(t) * SCENE_F; +} +pub fn frame_bytes() -> i32 { + return FRAME_W * FRAME_H * 3; +} + +// ---- assign ---- + +// Where card particle m of mm sits on the border of the rectangle (x0, y0)-(x1, y1): what 0 = x, +// 1 = y. The particles are spread evenly along the perimeter, clockwise from the top left. +pub fn card_point(m: i32, mm: i32, x0: i32, y0: i32, x1: i32, y1: i32, what: i32) -> i32 { + const w = x1 - x0; + const h = y1 - y0; + const d = @divTrunc(m * 2 * (w + h), mm); + var x : i32 = x0; + var y : i32 = y0; + if (d < w) { + x = x0 + d; + } else if (d < w + h) { + x = x1; + y = y0 + d - w; + } else if (d < 2 * w + h) { + x = x1 - (d - w - h); + y = y1; + } else { + y = y1 - (d - 2 * w - h); + } + if (what == 0) { return x; } + return y; +} +// The offset of text particle i inside its cell of size cs, for the m-th particle on that cell: +// one of nine sub-slots, plus a seeded jitter of up to cs / 8 so a dense cell does not stack. +pub fn slot_off(i: i32, m: i32, c: i32, cs: i32, axis: i32) -> i32 { + const slot = fmod(m * 4 + c, 9); + var k : i32 = fmod(slot, 3); + if (axis == 1) { k = @divTrunc(slot, 3); } + const j = @divTrunc(cs, 4) + 1; + return (k - 1) * @divTrunc(cs, 3) + rnd(i * 17 + axis * 5 + m, j) - @divTrunc(j, 2); +} + +// ---- render ---- + +pub fn main() -> void { + var x : [3072]i32 = undefined; + var y : [3072]i32 = undefined; + var vx : [3072]i32 = undefined; + var vy : [3072]i32 = undefined; + var tx : [3072]i32 = undefined; + var ty : [3072]i32 = undefined; + var sx : [3072]i32 = undefined; + var sy : [3072]i32 = undefined; + var cr : [3072]i32 = undefined; + var cg : [3072]i32 = undefined; + var cb : [3072]i32 = undefined; + var cellx : [1024]i32 = undefined; + var celly : [1024]i32 = undefined; + var kt : [1024]i32 = undefined; + var tt : [1024]i32 = undefined; + var accr : [1080]i32 = undefined; + var accg : [1080]i32 = undefined; + var accb : [1080]i32 = undefined; + var row : [3240]u8 = undefined; + + const p = PRESET; + const rr = reach(p); + var d2 : i32 = 0; + while (d2 <= rr * rr) { + kt[@as(usize, @intCast(d2))] = kern(p, d2); + d2 = d2 + 1; + } + var tv : i32 = 0; + while (tv < TONE_N) { + tt[@as(usize, @intCast(tv))] = tone(tv); + tv = tv + 1; + } + const bgr = red(BG[p]); + const bgg = green(BG[p]); + const bgb = blue(BG[p]); + const kr = red(KEY_COL[p]); + const kg = green(KEY_COL[p]); + const kb = blue(KEY_COL[p]); + const gmax = GRAIN[p]; + const ncards = @divTrunc(NP, 4); + + const frames = reel_frames(IDEA); + var frame : i32 = 0; + while (frame < frames) { + const s = @divTrunc(frame, SCENE_F); + const fis = frame - s * SCENE_F; + + // ---- a cut: every particle gets a target in the new scene ---- + if (fis == 0) { + const nc = imin(reel_cell(IDEA, s, 0, 2), MAXCELLS); + var c : i32 = 0; + while (c < nc) { + cellx[@as(usize, @intCast(c))] = reel_cell(IDEA, s, c, 0); + celly[@as(usize, @intCast(c))] = reel_cell(IDEA, s, c, 1); + c = c + 1; + } + const cs = reel_cell(IDEA, s, 0, 7); + const bx0 = reel_cell(IDEA, s, 0, 3) - cs; + const by0 = reel_cell(IDEA, s, 0, 4) - cs; + const bx1 = reel_cell(IDEA, s, 0, 5) + cs; + const by1 = reel_cell(IDEA, s, 0, 6) + cs; + var ia : i32 = 0; + while (ia < NP) { + const u = @as(usize, @intCast(ia)); + var gx : i32 = 0; + var gy : i32 = 0; + if (CARD[p] == 1 && fmod(ia, 4) == 3 && nc > 0) { + gx = card_point(@divTrunc(ia, 4), ncards, bx0, by0, bx1, by1, 0); + gy = card_point(@divTrunc(ia, 4), ncards, bx0, by0, bx1, by1, 1); + } else if (nc > 0) { + const ce = fmod(ia, nc); + const m = @divTrunc(ia, nc); + gx = cellx[@as(usize, @intCast(ce))] + slot_off(ia, m, ce, cs, 0); + gy = celly[@as(usize, @intCast(ce))] + slot_off(ia, m, ce, cs, 1); + } else { + gx = rnd(ia * 5 + s, FRAME_W); + gy = rnd(ia * 7 + s, FRAME_H); + } + tx[u] = gx * ONE; + ty[u] = gy * ONE; + if (s == 0) { + x[u] = start_x(p, ia, gx, CX, FRAME_W) * ONE; + y[u] = start_y(p, ia, gy, CY, FRAME_H) * ONE; + vx[u] = start_vx(p, ia); + vy[u] = start_vy(p, ia); + } else { + const dx = clamp(x[u] - CX * ONE, 0 - 1200 * ONE, 1200 * ONE); + const dy = clamp(y[u] - CY * ONE, 0 - 1200 * ONE, 1200 * ONE); + const h = rnd(ia * 29 + s, 256); + vx[u] = vx[u] + kick_x(p, dx, dy, h); + vy[u] = vy[u] + kick_y(p, dx, dy, h); + } + ia = ia + 1; + } + } + + // ---- physics ---- + const l = live(fis); + var st : i32 = 0; + while (st < STEPS) { + var ib : i32 = 0; + while (ib < NP) { + const u = @as(usize, @intCast(ib)); + const dx = clamp(x[u] - CX * ONE, 0 - 1200 * ONE, 1200 * ONE); + const dy = clamp(y[u] - CY * ONE, 0 - 1200 * ONE, 1200 * ONE); + const h = rnd(ib * 31 + frame * 2 + st, 256); + vx[u] = step_v(p, x[u], vx[u], tx[u], law_ax(p, dx, dy, l, h)); + vy[u] = step_v(p, y[u], vy[u], ty[u], law_ay(p, dx, dy, l, h, ty[u] - y[u])); + x[u] = x[u] + vx[u]; + y[u] = y[u] + vy[u]; + ib = ib + 1; + } + st = st + 1; + } + + // ---- paint: each particle's pixel and colour ---- + var ic : i32 = 0; + while (ic < NP) { + const u = @as(usize, @intCast(ic)); + sx[u] = @divTrunc(x[u], ONE); + sy[u] = @divTrunc(y[u], ONE); + const col = particle_col(p, ic, iabs(vx[u]) + iabs(vy[u])); + cr[u] = channel_of(p, red(col), bgr); + cg[u] = channel_of(p, green(col), bgg); + cb[u] = channel_of(p, blue(col), bgb); + ic = ic + 1; + } + + // ---- raster: one row at a time ---- + var gseed : i32 = 1000003 + frame * 7919; + var py : i32 = 0; + while (py < FRAME_H) { + var q : i32 = 0; + while (q < FRAME_W) { + const k = @as(usize, @intCast(q)); + accr[k] = 0; + accg[k] = 0; + accb[k] = 0; + q = q + 1; + } + var j : i32 = 0; + while (j < NP) { + const u = @as(usize, @intCast(j)); + const dy = py - sy[u]; + if (dy <= rr && dy >= 0 - rr) { + q = imax(sx[u] - rr, 0); + const qe = imin(sx[u] + rr, FRAME_W - 1); + while (q <= qe) { + const dx = q - sx[u]; + const dd = dx * dx + dy * dy; + if (dd <= rr * rr) { + const w = kt[@as(usize, @intCast(dd))]; + const k = @as(usize, @intCast(q)); + accr[k] = accr[k] + w * cr[u]; + accg[k] = accg[k] + w * cg[u]; + accb[k] = accb[k] + w * cb[u]; + } + q = q + 1; + } + } + j = j + 1; + } + q = 0; + while (q < FRAME_W) { + const k = @as(usize, @intCast(q)); + const vig = vignette(p, q, py, FRAME_W, FRAME_H); + const lift = key_lift(p, q, py, FRAME_W, FRAME_H); + gseed = lcg(gseed); + const g = fmod(@divTrunc(gseed, 4096), 2 * gmax + 1) - gmax; + const lr = tt[@as(usize, @intCast(clamp(@divTrunc(accr[k], ONE), 0, TONE_N - 1)))]; + const lg = tt[@as(usize, @intCast(clamp(@divTrunc(accg[k], ONE), 0, TONE_N - 1)))]; + const lb = tt[@as(usize, @intCast(clamp(@divTrunc(accb[k], ONE), 0, TONE_N - 1)))]; + row[k * 3] = @as(u8, @intCast(finish(p, bgr, kr, lift, lr, vig, g))); + row[k * 3 + 1] = @as(u8, @intCast(finish(p, bgg, kg, lift, lg, vig, g))); + row[k * 3 + 2] = @as(u8, @intCast(finish(p, bgb, kb, lift, lb, vig, g))); + q = q + 1; + } + std.debug.print("{s}", .{row}); + py = py + 1; + } + frame = frame + 1; + } +} + +// ---- tests ---- + +test "the sample idea wraps into seven lines and two scenes, and the reel adds the address" { + assert(line_count(IDEA) == 7); + assert(scene_count(IDEA) == 2); + assert(reel_scenes(IDEA) == 3); + assert(wrap_walk(IDEA, 0, 1) == 9); // PARTICLES + assert(wrap_walk(IDEA, 3, 2) == 0); // FRAME closes scene 0 + assert(wrap_walk(IDEA, 4, 1) == 8); // ONE SPEC + assert(wrap_walk(IDEA, 4, 2) == 1); + assert(wrap_walk(IDEA, 7, 0) == 0 - 1); + assert(reel_frames(IDEA) == 216); +} + +test "a 40-character idea with no cut wraps into lines of at most 9 and scenes of at most 4" { + const t = "SPRINGS PULL EVERY DOT TO ITS LETTER NOW"; + assert(t.len == 40); + const n = line_count(t); + assert(n >= 5); + var k : i32 = 0; + var per : i32 = 0; + var last : i32 = 0; + while (k < n) { + assert(wrap_walk(t, k, 1) <= MAXC); + assert(wrap_walk(t, k, 1) > 0); + const s = wrap_walk(t, k, 2); + if (s == last) { per = per + 1; } else { per = 1; last = s; } + assert(per <= MAXL); + k = k + 1; + } + assert(scene_count(t) == 2); +} + +test "a word longer than a line is split, and empty cuts make no scene" { + assert(line_count("ABCDEFGHIJKLMNOPQRSTUVWXYZ") == 3); + assert(wrap_walk("ABCDEFGHIJKLMNOPQRSTUVWXYZ", 2, 1) == 8); + assert(scene_count("A||B") == 2); + assert(scene_count("") == 0); + assert(reel_scenes("") == 1); +} + +test "cells are the font's lit pixels: AA lights twice A, a space lights none" { + const a = scene_cell("A", 0, 0, 2); + assert(a > 0); + assert(scene_cell("AA", 0, 0, 2) == 2 * a); + assert(scene_cell("A A", 0, 0, 2) == 2 * a); + assert(scene_cell(" ", 0, 0, 2) == 0); + assert(scene_cell("a", 0, 0, 2) == a); +} + +test "every target of every scene lies inside the frame, and the card frames the text" { + var s : i32 = 0; + while (s < reel_scenes(IDEA)) { + const n = reel_cell(IDEA, s, 0, 2); + assert(n > 0); + assert(n <= MAXCELLS); + const cs = reel_cell(IDEA, s, 0, 7); + assert(cs >= 8 && cs <= CELL_MAX); + var c : i32 = 0; + while (c < n) { + const cx = reel_cell(IDEA, s, c, 0); + const cy = reel_cell(IDEA, s, c, 1); + assert(cx > reel_cell(IDEA, s, 0, 3) && cx < reel_cell(IDEA, s, 0, 5)); + assert(cy > reel_cell(IDEA, s, 0, 4) && cy < reel_cell(IDEA, s, 0, 6)); + c = c + 1; + } + assert(reel_cell(IDEA, s, 0, 3) - cs >= 0); + assert(reel_cell(IDEA, s, 0, 5) + cs < FRAME_W); + assert(reel_cell(IDEA, s, 0, 4) - cs >= 0); + assert(reel_cell(IDEA, s, 0, 6) + cs < FRAME_H); + s = s + 1; + } +} + +test "the card's particles walk the whole border once" { + assert(card_point(0, 100, 10, 20, 110, 70, 0) == 10); + assert(card_point(0, 100, 10, 20, 110, 70, 1) == 20); + assert(card_point(25, 100, 10, 20, 110, 70, 0) == 85); + assert(card_point(50, 100, 10, 20, 110, 70, 0) == 110); + assert(card_point(50, 100, 10, 20, 110, 70, 1) == 70); + assert(card_point(99, 100, 10, 20, 110, 70, 0) == 10); +} + +test "sub-slots stay inside their cell" { + var i : i32 = 0; + while (i < 200) { + const ox = slot_off(i, fmod(i, 13), fmod(i, 7), 25, 0); + const oy = slot_off(i, fmod(i, 13), fmod(i, 7), 25, 1); + assert(iabs(ox) <= 12); + assert(iabs(oy) <= 12); + i = i + 1; + } +} + +test "a frame is 1080x1920 RGB24, 6220800 bytes" { + assert(frame_bytes() == 6220800); + assert(FPS == 30); +} diff --git a/specs/t27f/particle_ds.t27 b/specs/t27f/particle_ds.t27 new file mode 100644 index 0000000000..890e47375c --- /dev/null +++ b/specs/t27f/particle_ds.t27 @@ -0,0 +1,419 @@ +// SPDX-License-Identifier: Apache-2.0 +// specs/t27f/particle_ds.t27 -- the particle design system of the OS: every visible element is a +// particle on a spring, and a style is a row of integers. Issue gHashTag/t27#10771, epic #10560. +// +// WHY: the owner, 2026-10-19: one content factory, on the factory pattern, that unpacks any idea in +// any style preset; the factory and every element are formed by particle physics; and in the end +// this behaviour runs inside the OS itself, on the FPGA too -- it is the visual design system of +// the OS. Until now each reel (token_floor, reel_particles, lesson_reel) carried its own constants +// in f64, so a style could not be swapped and nothing could move to hardware. +// +// THE DESIGN SYSTEM, in three parts: +// tokens PRESETS rows of integers: three colours, a blend, a particle radius and gain, a +// spring, a dashpot, a force law with its strength, a transition kick, a card flag and +// a colour rule. A new style is a new column in these tables, never new code. +// physics one integrator step for one particle, per axis: a clamped linear spring toward its +// target plus a dashpot (semi-implicit Euler: v += a, then x += v), plus the preset's +// law -- gravity (drop, rain), a swirl (vortex), a seeded jitter (drift) -- that fades +// with the scene's `live` clock, and a kick when the scene cuts. +// paint a particle is a soft disc w = GAIN r^4 / (r^2 + d^2)^2, summed per pixel and rolled +// off by a tone curve; an ink preset subtracts from a light page instead of adding light. +// +// THE NUMBERS: positions and velocities are Q8 fixed point (256 = one pixel, one pixel per integrator +// step), colours are 0xRRGGBB in an i32, angles are a 16-step table. The per-step and per-pixel paths +// use only add, multiply, compare, table lookup and division by a power of two (q8, q16): no float, +// no square root, no division by a variable, so the same law can follow the FPGA path. kern() and +// tone() divide, but only to fill a ROM once (idea_factory builds the tables before the first frame). +// +// NOT CLAIMED: the spring rates, laws and colours are design choices, labelled as such; no constant +// here is a physical measurement. This module has not been run through gen-verilog yet, and no +// board has drawn it; it is written so that it can be. +// phi^2 + 1/phi^2 = 3 | TRINITY + +module particle_ds; + +use t27f::scene_raster::rgb; +use t27f::scene_raster::red; +use t27f::scene_raster::green; +use t27f::scene_raster::blue; +use t27f::scene_raster::clamp; +use t27f::scene_raster::iabs; +use t27f::scene_raster::rnd; +use t27f::scene_raster::fmod; + +pub const ID : str = "particle-ds"; +pub const VERSION : u8 = 1; + +// ---- fixed point ---- +pub const ONE : i32 = 256; // one pixel, Q8 +pub const AMAX : i32 = 768; // the largest acceleration, 3 px per step^2 +pub const VMAX : i32 = 12288; // the largest speed, 48 px per step +pub const LIVE_STEP : i32 = 6; // the law's clock falls 6/256 a frame, gone after 43 frames + +// ---- the presets (design tokens) ---- +pub const PRESETS : usize = 5; +pub const P_NOIR : usize = 0; // gold on near-black, letters fall into place +pub const P_NEON : usize = 1; // cyan and magenta on blue-black, a vortex winds them in +pub const P_TRIT : usize = 2; // -1, 0, +1 charges burst from the centre +pub const P_PAPER : usize = 3; // ink on a cream page, drifting like dust +pub const P_RAIN : usize = 4; // green rain falls and holds as letters +pub const PRESET_NAMES : str = "noir-gold,neon-vortex,trit-charge,paper-ink,rain"; + +pub const LAW_DROP : i32 = 0; +pub const LAW_VORTEX : i32 = 1; +pub const LAW_BURST : i32 = 2; +pub const LAW_DRIFT : i32 = 3; +pub const LAW_RAIN : i32 = 4; + +pub const BLEND_ADD : i32 = 0; // light adds on a dark field +pub const BLEND_INK : i32 = 1; // ink subtracts from a light page + +pub const MIX_HEAT : i32 = 0; // colour from A to B as the particle speeds up +pub const MIX_TRIT : i32 = 1; // colour from the particle's trit: A = -1, B = 0, C = +1 + +// noir neon trit paper rain +pub const BG : [5]i32 = [0x0A0A0A, 0x06060F, 0x080808, 0xECE6DA, 0x000602]; +pub const COL_A : [5]i32 = [0xFFC45C, 0x00E5FF, 0xFF5A5A, 0x18161C, 0x00FF66]; +pub const COL_B : [5]i32 = [0xFFF1DC, 0xFF2BD6, 0xEBEBEB, 0xC41E28, 0xBEFFD2]; +pub const COL_C : [5]i32 = [0x8C5A1E, 0x7850FF, 0x50A0FF, 0x5A5650, 0x008C3C]; +pub const KEY_COL : [5]i32 = [0xFFF1DC, 0x7850FF, 0xEBEBEB, 0xFFFFFF, 0x00FF66]; +pub const KEY_LIFT : [5]i32 = [18, 14, 10, 8, 10]; // levels the key light adds at its centre +pub const VIGN : [5]i32 = [20, 22, 18, 8, 22]; // how much the corners darken +pub const GRAIN : [5]i32 = [3, 3, 3, 4, 4]; // levels, peak to peak +pub const BLEND : [5]i32 = [0, 0, 0, 1, 0]; +pub const MIX : [5]i32 = [0, 0, 1, 0, 0]; +pub const RADIUS : [5]i32 = [5, 5, 6, 4, 4]; // px +pub const GAIN : [5]i32 = [200, 210, 190, 230, 190]; // a lone particle's peak, of 256 +pub const SPRING : [5]i32 = [5, 4, 6, 3, 7]; // Q8 per step^2 per Q8 of distance +pub const DAMP : [5]i32 = [46, 42, 51, 42, 55]; // Q8 per step: damping ratio 0.65-0.75 +pub const LAW : [5]i32 = [0, 1, 2, 3, 4]; +pub const FORCE : [5]i32 = [300, 50, 0, 200, 500]; // the law's strength, see law_ax/law_ay +pub const KICK : [5]i32 = [2600, 3000, 220, 1600, 1200]; +pub const CARD : [5]i32 = [1, 1, 0, 1, 0]; // a card border of particles behind the text + +// cos of k * 2 pi / 16, Q8; sin(k) = COS16[(k + 12) % 16]. +pub const COS16 : [16]i32 = [256, 237, 181, 98, 0, -98, -181, -237, -256, -237, -181, -98, 0, 98, 181, 237]; + +// ---- integer helpers ---- + +// Division by 256 and by 65536, rounding toward zero (a shift and a sign fix in hardware). +pub fn q8(a: i32) -> i32 { + return @divTrunc(a, 256); +} +pub fn q16(a: i32) -> i32 { + return @divTrunc(a, 65536); +} +pub fn dir_cos(k: i32) -> i32 { + return COS16[@as(usize, @intCast(fmod(k, 16)))]; +} +pub fn dir_sin(k: i32) -> i32 { + return COS16[@as(usize, @intCast(fmod(k + 12, 16)))]; +} + +// ---- physics: one step of one particle ---- + +// The scene's law clock, of 256: full when the scene cuts, gone LIVE_STEP frames later per level. +pub fn live(frame_in_scene: i32) -> i32 { + return clamp(ONE - LIVE_STEP * frame_in_scene, 0, ONE); +} + +// The spring toward t and the dashpot on v, Q8 px per step^2, clamped to AMAX. +pub fn spring_a(p: usize, x: i32, v: i32, t: i32) -> i32 { + const a = q8(SPRING[p] * (t - x)) - q8(DAMP[p] * v); + return clamp(a, 0 - AMAX, AMAX); +} + +// The preset's law along x and y. dx, dy: the particle from the scene's centre, Q8 px; h: a seeded +// byte 0..255 for this particle and step; l: the law clock; gap: the target below the particle, Q8 +// (gravity pulls only a particle still above its place, so a settled letter does not sag). +pub fn law_ax(p: usize, dx: i32, dy: i32, l: i32, h: i32) -> i32 { + const law = LAW[p]; + if (law == LAW_VORTEX) { return q8(q16(0 - dy * FORCE[p]) * l); } + if (law == LAW_DRIFT) { return q16((h - 128) * FORCE[p] * l); } + return 0; +} +pub fn law_ay(p: usize, dx: i32, dy: i32, l: i32, h: i32, gap: i32) -> i32 { + const law = LAW[p]; + if (law == LAW_DROP || law == LAW_RAIN) { + if (gap > 4 * ONE) { return q8(FORCE[p] * l); } + return 0; + } + if (law == LAW_VORTEX) { return q8(q16(dx * FORCE[p]) * l); } + if (law == LAW_DRIFT) { return q16((128 - h) * FORCE[p] * l); } + return 0; +} + +// One step of one axis: the new velocity (semi-implicit Euler; the caller then adds it to x). +pub fn step_v(p: usize, x: i32, v: i32, t: i32, law_a: i32) -> i32 { + return clamp(v + spring_a(p, x, v, t) + law_a, 0 - VMAX, VMAX); +} + +// The kick when a scene cuts, per axis: a hop (drop), a turn (vortex), a blast out of the centre +// (burst), a shove (drift), a push down (rain). +pub fn kick_x(p: usize, dx: i32, dy: i32, h: i32) -> i32 { + const law = LAW[p]; + if (law == LAW_VORTEX) { return q16(0 - dy * KICK[p]); } + if (law == LAW_BURST) { return @divTrunc(q8(dx * KICK[p]), 16); } + if (law == LAW_DRIFT) { return q8((h - 128) * KICK[p]); } + return 0; +} +pub fn kick_y(p: usize, dx: i32, dy: i32, h: i32) -> i32 { + const law = LAW[p]; + if (law == LAW_DROP) { return 0 - KICK[p]; } + if (law == LAW_VORTEX) { return q16(dx * KICK[p]); } + if (law == LAW_BURST) { return @divTrunc(q8(dy * KICK[p]), 16); } + if (law == LAW_DRIFT) { return q8((128 - h) * KICK[p]); } + if (law == LAW_RAIN) { return KICK[p]; } + return 0; +} + +// Where particle i starts, in px, given its first target (tx, ty) and the frame's centre (cx, cy). +pub fn start_x(p: usize, i: i32, tx: i32, cx: i32, w: i32) -> i32 { + const law = LAW[p]; + if (law == LAW_VORTEX) { return cx + q8(dir_cos(i) * (300 + rnd(i * 3 + 1, 420))); } + if (law == LAW_BURST) { return cx + q8(dir_cos(i) * rnd(i * 3 + 1, 40)); } + if (law == LAW_DRIFT) { return rnd(i * 5 + 2, w); } + return tx + rnd(i * 7 + 3, 9) - 4; +} +pub fn start_y(p: usize, i: i32, ty: i32, cy: i32, h: i32) -> i32 { + const law = LAW[p]; + if (law == LAW_VORTEX) { return cy + q8(dir_sin(i) * (300 + rnd(i * 3 + 1, 420))); } + if (law == LAW_BURST) { return cy + q8(dir_sin(i) * rnd(i * 3 + 1, 40)); } + if (law == LAW_DRIFT) { return rnd(i * 5 + 4, h); } + if (law == LAW_RAIN) { return 0 - 40 - rnd(i * 11 + 5, 2400); } + return 0 - 200 - rnd(i * 11 + 5, 1100); +} +// A burst starts already flying outward. +pub fn start_vx(p: usize, i: i32) -> i32 { + if (LAW[p] == LAW_BURST) { return q8(dir_cos(i) * (KICK[p] * 20 + rnd(i * 13 + 6, 2000))); } + return 0; +} +pub fn start_vy(p: usize, i: i32) -> i32 { + if (LAW[p] == LAW_BURST) { return q8(dir_sin(i) * (KICK[p] * 20 + rnd(i * 13 + 6, 2000))); } + return 0; +} + +// ---- paint ---- + +// The colour of particle i at speed s (|vx| + |vy|, Q8). +pub fn particle_col(p: usize, i: i32, s: i32) -> i32 { + if (MIX[p] == MIX_TRIT) { + const t = fmod(i, 3); + if (t == 0) { return COL_A[p]; } + if (t == 1) { return COL_B[p]; } + return COL_C[p]; + } + if (fmod(i, 7) == 0) { return COL_C[p]; } + const k = clamp(q8(s * 64), 0, 256); + const r = red(COL_A[p]) + q8((red(COL_B[p]) - red(COL_A[p])) * k); + const g = green(COL_A[p]) + q8((green(COL_B[p]) - green(COL_A[p])) * k); + const b = blue(COL_A[p]) + q8((blue(COL_B[p]) - blue(COL_A[p])) * k); + return rgb(r, g, b); +} + +// What one channel of a particle adds to the pixel sum: light for ADD, the ink's depth for INK. +pub fn channel_of(p: usize, col_ch: i32, bg_ch: i32) -> i32 { + if (BLEND[p] == BLEND_INK) { return bg_ch - col_ch; } + return col_ch; +} + +// The kernel's reach: past 3 r a particle adds under GAIN / 100. +pub fn reach(p: usize) -> i32 { + return 3 * RADIUS[p]; +} +// The kernel at squared distance d2 (px^2), of 256: GAIN r^4 / (r^2 + d2)^2. Fills a ROM once. +pub fn kern(p: usize, d2: i32) -> i32 { + const r2 = RADIUS[p] * RADIUS[p]; + const q = r2 + d2; + return @divTrunc(GAIN[p] * r2 * r2, q * q); +} +// The tone curve: the identity up to 200, then a roll-off toward 255. Fills a ROM once. +pub fn tone(x: i32) -> i32 { + if (x <= 200) { return x; } + const e = x - 200; + return 200 + @divTrunc(55 * e, e + 55); +} +pub const TONE_N : i32 = 1024; + +// The darkening toward the corners, of 256, at (x, y) in px on a w x h frame. +pub fn vignette(p: usize, x: i32, y: i32, w: i32, h: i32) -> i32 { + const dx = x - @divTrunc(w, 2); + const dy = y - @divTrunc(h, 2); + const d = @divTrunc(q8(dx * dx + dy * dy), 16); + return ONE - q8(clamp(d, 0, 300) * VIGN[p]) * 3; +} +// The key light's lift at (x, y): KEY_LIFT at its centre (w / 2, 0.42 h), gone 700 px away. +pub fn key_lift(p: usize, x: i32, y: i32, w: i32, h: i32) -> i32 { + const dx = x - @divTrunc(w, 2); + const dy = y - q8(h * 108); + const d = q16(dx * dx + dy * dy); + return clamp(KEY_LIFT[p] - q8(d * KEY_LIFT[p] * 34), 0, KEY_LIFT[p]); +} +// The final level of one channel: background, key light, the toned particle sum t, vignette, grain. +pub fn finish(p: usize, bg_ch: i32, key_ch: i32, lift: i32, t: i32, vig: i32, grain: i32) -> i32 { + var v : i32 = 0; + if (BLEND[p] == BLEND_INK) { + v = bg_ch - t; + } else { + v = bg_ch + q8(key_ch * lift) + t; + } + return clamp(q8(v * vig) + grain, 0, 255); +} + +// ---- tests ---- + +test "five presets, five names, and every table has a row for each" { + var commas : i32 = 0; + var k : usize = 0; + while (k < PRESET_NAMES.len) { + if (PRESET_NAMES[k] == 44) { commas = commas + 1; } + k = k + 1; + } + assert(commas + 1 == 5); + assert(PRESETS == 5); + assert(LAW[P_NOIR] == LAW_DROP); + assert(LAW[P_NEON] == LAW_VORTEX); + assert(LAW[P_TRIT] == LAW_BURST); + assert(LAW[P_PAPER] == LAW_DRIFT); + assert(LAW[P_RAIN] == LAW_RAIN); + assert(BLEND[P_PAPER] == BLEND_INK); + assert(MIX[P_TRIT] == MIX_TRIT); +} + +test "no two presets share a palette" { + var a : usize = 0; + while (a < PRESETS) { + var b : usize = a + 1; + while (b < PRESETS) { + assert(BG[a] != BG[b] || COL_A[a] != COL_A[b]); + b = b + 1; + } + a = a + 1; + } +} + +test "q8 rounds toward zero on both sides, so a dashpot never drifts one way" { + assert(q8(255) == 0); + assert(q8(0 - 255) == 0); + assert(q8(256) == 1); + assert(q8(0 - 256) == 0 - 1); + assert(q16(0 - 65535) == 0); +} + +test "the direction table is a circle: cos^2 + sin^2 is 256^2 within 1%" { + var k : i32 = 0; + while (k < 16) { + const c = dir_cos(k); + const s = dir_sin(k); + const n = c * c + s * s; + assert(n > 64880 && n < 66190); + k = k + 1; + } + assert(dir_sin(4) == 256); + assert(dir_cos(8) == 0 - 256); +} + +test "every preset's spring settles a particle 1000 px away inside one 72-frame scene, overshooting under 120 px" { + var p : usize = 0; + while (p < PRESETS) { + var x : i32 = 0; + var v : i32 = 0; + const t : i32 = 1000 * ONE; + var over : i32 = 0; + var st : i32 = 0; + while (st < 144) { + v = step_v(p, x, v, t, 0); + x = x + v; + if (x - t > over) { over = x - t; } + st = st + 1; + } + assert(iabs(x - t) < 2 * ONE); + assert(iabs(v) < ONE); + assert(over < 120 * ONE); + p = p + 1; + } +} + +test "the law clock is full at the cut and gone before the next" { + assert(live(0) == 256); + assert(live(20) == 136); + assert(live(43) == 0); + assert(live(71) == 0); +} + +test "laws push the way they say" { + assert(law_ay(P_NOIR, 0, 0, 256, 0, 1000 * ONE) == 300); + assert(law_ay(P_RAIN, 0, 0, 256, 0, 1000 * ONE) > law_ay(P_NOIR, 0, 0, 256, 0, 1000 * ONE)); + assert(law_ax(P_NOIR, 1000, 1000, 256, 77) == 0); + assert(law_ay(P_NOIR, 0, 0, 0, 0, 1000 * ONE) == 0); + // the vortex turns: a particle right of centre is pushed down, one below it is pushed left + assert(law_ay(P_NEON, 400 * ONE, 0, 256, 0, 1000 * ONE) > 0); + assert(law_ax(P_NEON, 0, 400 * ONE, 256, 0) < 0); + // a burst kicks outward + assert(kick_x(P_TRIT, 100 * ONE, 0, 0) > 0); + assert(kick_y(P_TRIT, 0, 0 - 100 * ONE, 0) < 0); + assert(kick_y(P_NOIR, 0, 0, 0) < 0); + assert(kick_y(P_RAIN, 0, 0, 0) > 0); +} + +test "starts: rain and drop above the frame, burst at the centre, drift inside it" { + var i : i32 = 0; + while (i < 64) { + assert(start_y(P_NOIR, i, 900, 960, 1920) < 0); + assert(start_y(P_RAIN, i, 900, 960, 1920) < 0); + assert(iabs(start_x(P_TRIT, i, 0, 540, 1080) - 540) <= 40); + const dx = start_x(P_PAPER, i, 0, 540, 1080); + assert(dx >= 0 && dx < 1080); + assert(iabs(start_vx(P_TRIT, i)) + iabs(start_vy(P_TRIT, i)) > 0); + assert(start_vx(P_NOIR, i) == 0); + i = i + 1; + } +} + +test "colours: a still particle is colour A, a fast one is colour B, a trit picks its own" { + assert(particle_col(P_NOIR, 1, 0) == COL_A[P_NOIR]); + assert(particle_col(P_NOIR, 1, 4096) == COL_B[P_NOIR]); + assert(particle_col(P_NOIR, 7, 0) == COL_C[P_NOIR]); + assert(particle_col(P_TRIT, 0, 9999) == COL_A[P_TRIT]); + assert(particle_col(P_TRIT, 1, 0) == COL_B[P_TRIT]); + assert(particle_col(P_TRIT, 2, 0) == COL_C[P_TRIT]); + assert(channel_of(P_PAPER, 24, 236) == 212); + assert(channel_of(P_NOIR, 196, 10) == 196); +} + +test "the kernel peaks at GAIN, falls with distance, and is under 1% of it at its reach" { + var p : usize = 0; + while (p < PRESETS) { + assert(kern(p, 0) == GAIN[p]); + var d2 : i32 = 1; + var last : i32 = kern(p, 0); + while (d2 <= reach(p) * reach(p)) { + const k = kern(p, d2); + assert(k <= last); + last = k; + d2 = d2 + 1; + } + assert(last * 100 <= GAIN[p] + 100); + p = p + 1; + } +} + +test "tone keeps the dark levels and never clips past 255" { + assert(tone(0) == 0); + assert(tone(200) == 200); + assert(tone(255) == 227); + assert(tone(TONE_N - 1) <= 255); + assert(tone(TONE_N - 1) >= 250); +} + +test "finish: the dark field stays near-black at the corners, the page stays cream" { + const vc = vignette(P_NOIR, 0, 0, 1080, 1920); + assert(vc < 256 && vc > 150); + assert(vignette(P_NOIR, 540, 960, 1080, 1920) == 256); + assert(key_lift(P_NOIR, 540, 806, 1080, 1920) == KEY_LIFT[P_NOIR]); + assert(key_lift(P_NOIR, 540, 1900, 1080, 1920) == 0); + assert(finish(P_NOIR, 10, 255, 0, 0, 256, 0) == 10); + assert(finish(P_PAPER, 236, 255, 0, 0, 256, 0) == 236); + assert(finish(P_PAPER, 236, 255, 0, 212, 256, 0) == 24); + assert(finish(P_NOIR, 10, 255, 0, 400, 256, 0) == 255); +} From 2c619fde88f29f859cb3c2d25837775cafa65f96 Mon Sep 17 00:00:00 2001 From: Dmitrii Vasilev Date: Mon, 19 Oct 2026 09:29:12 +0700 Subject: [PATCH 02/21] t27f skill: idea factory and the particle design system, preset sweep on the lab, legibility by luma (Refs #10771) Co-Authored-By: Claude Opus 5.5 --- .claude/skills/t27f/SKILL.md | 46 ++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/.claude/skills/t27f/SKILL.md b/.claude/skills/t27f/SKILL.md index d1f16ef389..b2475da521 100644 --- a/.claude/skills/t27f/SKILL.md +++ b/.claude/skills/t27f/SKILL.md @@ -396,6 +396,52 @@ Measured at 355d44a39: each still has 9 to 14 flat colours, and the PNGs are 4 t `foreign-budget` hook on master's own Rust lines (t27#10414). Merge the old branch into a master-based branch instead. The result still fast-forwards the old branch. +## Application: idea factory, the OS particle design system (t27#10771) + +The owner wants one factory that unpacks any idea in any style preset, with every element made +of particles, and runnable in the OS itself and on FPGA. Two specs do this: + +- **`specs/t27f/particle_ds.t27` is the design system.** A preset is a column in integer tables: + `BG`, `COL_A/B/C`, `RADIUS`, `GAIN`, `SPRING`, `DAMP`, `LAW`, `FORCE`, `KICK`, `CARD`. The five + are noir, neon, trit, paper and rain. **Add a preset by adding a column, never a branch in code.** +- **`specs/t27f/idea_factory.t27` is the factory**, `(IDEA, PRESET) -> RGB24 frames`. It wraps + (at most 9 characters per line, 4 lines per scene, `|` cuts a scene), lays out in the 3x5 font, + assigns particles to glyph sub-slots and the card border, and closes on `CTA`. + +**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. +Divide by a variable at most once per scene cut, never per pixel. The seals then carry +`gen_hash_verilog`. That proves Verilog is emitted, not that it was synthesised. Say so. + +**Render the preset sweep on the lab:** + +1. For each preset, `sed` the `PRESET` constant. Also rename the module (for example + `idea_factory_p`), so the gen output does not overwrite the committed module's. +2. Run `t27c gen`, then `zig build-exe -O ReleaseFast`. +3. Run `./f 2>&1 1>fo.txt | ffmpeg -f rawvideo -pix_fmt rgb24 -s 1080x1920 -r 30 -i - ...` + (libx264, crf 19). The frames go to stderr. Send stdout to a file, **never to `/dev/null`**: + the lab replays `/dev/null` writes. +4. Concatenate the five, scale to 720 and make a tile sheet. Fetch them as `B64:...:END` and + check sha256 on both ends. + +Measured: each preset takes 14 to 35 s for 216 frames, with 3072 particles. + +**Check legibility by numbers, because the Mac cannot read images.** Crop the settled frame at +t = 2.2 s with ffmpeg and read `signalstats` YAVG for three regions: the text block, a gap row +between lines, and the empty bottom. On a dark preset you want block > gap > bottom (rain: +44.8 > 25.4 > 19.0). On paper the order flips (block 187 < gap 211). A flat triple means the +particles never settled into letters. + +**Traps:** + +- **Read the `test-report` tail, not a grep.** A grep for pass and fail counts miscounts the + summary lines (`tests N`, `pass N`, `FAIL 0`, `vacuous passes 0 of N`). +- **Rename loop variables by line range.** A rename that only changes the declaration leaves the + body pointing at the old name. Give each top-level loop in `main` its own name (`ia`, `ib`, + `ic`). +- **Base the branch on master's tip before the PR.** If the base PR (R3) merged first, reset the branch onto + `origin/master`. Untracked new specs survive the reset. + ## Check the whole corpus for import cycles (t27#10693) dependency-cruiser's `no-circular` and madge's `--circular` list every module on a cycle. From 5e0761589082355731d392117b78771fa1845e37 Mon Sep 17 00:00:00 2001 From: Dmitrii Vasilev Date: Mon, 19 Oct 2026 09:45:52 +0700 Subject: [PATCH 03/21] t27f: Russian ideas in the factory -- UTF-8 and a 5x7 Latin and Cyrillic font of particles (Closes #10788) glyph57.t27 decodes UTF-8, folds lower case to capitals and stores a 5x7 font: ASCII 32..95 plus the 21 Cyrillic capitals with no Latin twin. The 11 look-alikes and Yo reuse Latin glyphs through a 32-entry map. idea_factory now wraps by characters, keeps positions in bytes, and sets text in the 5x7 font (advance 6, line pitch 9). t27c keeps "\xNN" in a string as four characters, so non-ASCII samples are u8 arrays (RU_IDEA, 80 bytes). Lab: glyph57 7/7, idea_factory 10/10, 0 vacuous; seals verified; dupe_scan clean. The t27f skill records the trap. Co-Authored-By: Claude Opus 5.5 --- .claude/skills/t27f/SKILL.md | 24 +- .trinity/seals/t27f_glyph57.json | 33 +++ .trinity/seals/t27f_idea_factory.json | 20 +- specs/t27f/glyph57.t27 | 380 ++++++++++++++++++++++++++ specs/t27f/idea_factory.t27 | 123 +++++++-- 5 files changed, 545 insertions(+), 35 deletions(-) create mode 100644 .trinity/seals/t27f_glyph57.json create mode 100644 specs/t27f/glyph57.t27 diff --git a/.claude/skills/t27f/SKILL.md b/.claude/skills/t27f/SKILL.md index b2475da521..f0920dfa51 100644 --- a/.claude/skills/t27f/SKILL.md +++ b/.claude/skills/t27f/SKILL.md @@ -405,7 +405,7 @@ of particles, and runnable in the OS itself and on FPGA. Two specs do this: `BG`, `COL_A/B/C`, `RADIUS`, `GAIN`, `SPRING`, `DAMP`, `LAW`, `FORCE`, `KICK`, `CARD`. The five are noir, neon, trit, paper and rain. **Add a preset by adding a column, never a branch in code.** - **`specs/t27f/idea_factory.t27` is the factory**, `(IDEA, PRESET) -> RGB24 frames`. It wraps - (at most 9 characters per line, 4 lines per scene, `|` cuts a scene), lays out in the 3x5 font, + (at most 9 characters per line, 4 lines per scene, `|` cuts a scene), lays out in glyph57's 5x7 font, assigns particles to glyph sub-slots and the card border, and closes on `CTA`. **Keep it FPGA-shaped.** Use Q8 fixed point. The per-step and per-pixel paths use only add, @@ -442,6 +442,28 @@ particles never settled into letters. - **Base the branch on master's tip before the PR.** If the base PR (R3) merged first, reset the branch onto `origin/master`. Untracked new specs survive the reset. +### Russian text: UTF-8 and the 5x7 font (t27#10788) + +`specs/t27f/glyph57.t27` gives the factory Cyrillic. It decodes UTF-8 (`cp_len`, `cp_at`, +`cp_count`), folds lower case to capitals, and stores a 5x7 font. The font holds ASCII 32..95 +and only the 21 Cyrillic capitals that have no Latin twin. The 11 look-alikes (A, Ve, Ie, Ka, Em, +En, O, Er, Es, Te, Ha) and Yo reuse the Latin glyph through the 32-entry `CYR_MAP`. + +- **Write non-ASCII text as a `u8` array, never as `"\xNN"`.** L3 keeps sources ASCII. The t27c + lexer lowers only `\n`, `\t`, `\\` and `\"`, and keeps `\xD0` as four characters, so a + `\xNN` test fails with no error. Use `pub const RU_IDEA : [80]u8 = [208, 167, ...];` and pass + `RU_IDEA[0..80]` to a `[]const u8` parameter. `specs/tri/utils/terminal.t27` records the same + trap for `\x1b`. +- **Count widths in characters, keep positions in bytes.** Wrapping compares `cp_count` with + `MAXC`. The line start and end stay byte offsets, and the layout loop steps by + `bi = bi + cp_len(t, bi)`. An ASCII idea's numbers do not change, so the old tests still pin + the old layout. +- **Guard the font with a duplicate-bitmap test.** Every pair of stored glyphs must differ, and + every look-alike must light exactly its Latin twin's pixels. +- **Render a Russian idea without touching the committed constant.** In the lab copy, `sed` + `reel_frames(IDEA)` and `reel_cell(IDEA,` to use `RU_IDEA[0..80]`, next to the `PRESET` and + module renames. + ## Check the whole corpus for import cycles (t27#10693) dependency-cruiser's `no-circular` and madge's `--circular` list every module on a cycle. diff --git a/.trinity/seals/t27f_glyph57.json b/.trinity/seals/t27f_glyph57.json new file mode 100644 index 0000000000..e7712fea8d --- /dev/null +++ b/.trinity/seals/t27f_glyph57.json @@ -0,0 +1,33 @@ +{ + "built_by": "t27c-bootstrap@0.5.2+355d44a39", + "closure": [ + { + "spec": "specs/t27f/scene_raster.t27", + "spec_hash": "sha256:7be7118d865aa65fd1ccb989ab160df2feb46c9ded0a15ca6510b747301ee4a6" + } + ], + "config": "gen=c,rust,verilog,zig;tests=zig", + "gen_hash_c": "sha256:7b616c3b258394696d96adfd9f33a21a788063f0fcc37c591d2982b9e5c44645", + "gen_hash_rust": "sha256:cfe5802136fe85e748ec9880d796a0ddb5ee6fe69a4de466dc544f373aa1bfab", + "gen_hash_verilog": "sha256:bc6fce86cf24c30a17e3d29e3afebdd28d1aed88c760196a055aec250d2a549e", + "gen_hash_zig": "sha256:d57c0bf3b612b865a9df137514ae0e66790196114c7111977ca43d795f67c0e4", + "module": "glyph57", + "ring": 12, + "seal_schema": 2, + "sealed_at": "2026-10-19T02:41:07Z", + "sealed_by": "t27c-bootstrap@0.5.2", + "spec_hash": "sha256:75e98adfcf86a4070c03154e29cf533833c618a1252652f2c20bf91b8425e6cd", + "spec_path": "specs/t27f/glyph57.t27", + "tests": { + "failed": 0, + "failing": [], + "forced": false, + "passed": 7, + "total": 7, + "vacuous": 0 + }, + "toolchain": { + "test_runner": "sha256:8512b80da533b79289110fca4a65f3336abd7a059ca4b72ea595d5e33ff8ad10", + "zig": "0.16.0" + } +} \ No newline at end of file diff --git a/.trinity/seals/t27f_idea_factory.json b/.trinity/seals/t27f_idea_factory.json index fff853f045..4f321c9bb3 100644 --- a/.trinity/seals/t27f_idea_factory.json +++ b/.trinity/seals/t27f_idea_factory.json @@ -1,6 +1,10 @@ { "built_by": "t27c-bootstrap@0.5.2+355d44a39", "closure": [ + { + "spec": "specs/t27f/glyph57.t27", + "spec_hash": "sha256:75e98adfcf86a4070c03154e29cf533833c618a1252652f2c20bf91b8425e6cd" + }, { "spec": "specs/t27f/particle_ds.t27", "spec_hash": "sha256:00e84a7492536bf54ac69aa0cd852dea4727477acee12538f9c4b145c3d9c8f3" @@ -11,23 +15,23 @@ } ], "config": "gen=c,rust,verilog,zig;tests=zig", - "gen_hash_c": "sha256:49e275c87108c8621d521226b89afb5f6c6a39f74de8e8bba502fbd1e39ad843", - "gen_hash_rust": "sha256:7d03e6e99a15344a1c8272b7f96ffc8ddcbfdb1cf44ab7f70f7a0e37847529bc", - "gen_hash_verilog": "sha256:0f761cb6e48d10ac555e51b8e5722eb8b02074a87ca2055aaec9241754140c49", - "gen_hash_zig": "sha256:0730d911d7047dc707075d16c8ff04afe222f4422a24344eff10b90e72edfdf0", + "gen_hash_c": "sha256:6a6ffcf30791e08b93221c1067abfc7e053f13c23dff90de4d9f7c9e23461479", + "gen_hash_rust": "sha256:d64150e24319f74e2b9413d45cfbd96a7d8bbc0df9d51c9fac0b68c8837b2efb", + "gen_hash_verilog": "sha256:387961ee44ce4ce7f32d4ccb23a4ac58ac6ef688db3b4c8aef38d1c60e41cc18", + "gen_hash_zig": "sha256:115d3f8ff262b6fd65b59d760aeb7eff66500b9f77f56577fb22233fefaa65e7", "module": "idea_factory", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-19T02:25:39Z", + "sealed_at": "2026-10-19T02:41:09Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:34f59b06d780be9cbbae1b0577e7c96ed5b6b7d2451e61ada1b1dde2fd58b6b8", + "spec_hash": "sha256:249909fbd1679b488be14be323c0079735a55895560423934c21daf28c824db9", "spec_path": "specs/t27f/idea_factory.t27", "tests": { "failed": 0, "failing": [], "forced": false, - "passed": 8, - "total": 8, + "passed": 10, + "total": 10, "vacuous": 0 }, "toolchain": { diff --git a/specs/t27f/glyph57.t27 b/specs/t27f/glyph57.t27 new file mode 100644 index 0000000000..8c40966697 --- /dev/null +++ b/specs/t27f/glyph57.t27 @@ -0,0 +1,380 @@ +// SPDX-License-Identifier: Apache-2.0 +// specs/t27f/glyph57.t27 -- the 5x7 particle font of the OS design system: UTF-8 in, lit cells +// out, Latin and Cyrillic. Issue gHashTag/t27#10788, epic #10560. +// +// WHY: the idea factory (idea_factory.t27) set its text in scene_raster's 3x5 font, which is ASCII +// only and too small for Zh, Sh, Shch or Yu. The owner's reels are Russian; until now their text +// was a TTF font drawn by ffmpeg drawtext -- not particles, and nothing an FPGA could draw. +// +// THE PIECES, all integer (lookups and compares, no division by a variable): +// decode cp_len / cp_at / cp_count read UTF-8 bytes. A byte that does not start a well-formed +// sequence reads as one character, BAD, so a walk never stalls and never skips text. +// fold Latin a..z, Cyrillic a..ya and yo fold to upper case: the font has capitals only. +// glyph ASCII 32..95 are glyphs 0..63; the 21 Cyrillic capitals with no Latin look-alike +// are glyphs 64..84; the 11 look-alikes (A Ve Ie Ka Em En O Er Es Te Ha) and Yo reuse +// the Latin glyph through CYR_MAP. Anything else draws as '?'. +// +// SOURCES ARE ASCII (L3), AND t27c DOES NOT LOWER "\xNN" IN A STRING (it keeps the backslash), so +// non-ASCII text is a u8 array of its UTF-8 bytes: Zh (U+0416) is [208, 150]. +// +// NOT CLAIMED: no lower-case glyphs (they fold); Yo is drawn as Ie, as on small LCD fonts; no +// glyph outside Latin, digits, ASCII punctuation and the Russian alphabet. +// phi^2 + 1/phi^2 = 3 | TRINITY + +module glyph57; + +use t27f::scene_raster::fmod; + +pub const ID : str = "glyph57"; +pub const VERSION : u8 = 1; + +pub const GW : i32 = 5; // glyph width, font pixels +pub const GH : i32 = 7; // glyph height +pub const GLYPHS57 : i32 = 85; // 64 ASCII + 21 Cyrillic +pub const BAD : i32 = 65533; // U+FFFD, what a malformed byte reads as +pub const QMARK : i32 = 31; // the glyph of '?' +pub const CYR_A : i32 = 1040; // U+0410, Cyrillic capital A +pub const CYR_YO : i32 = 1025; // U+0401, Cyrillic capital Yo + +// The weight of pixel column gx in a row: bit 4 is the left pixel. +pub const PW5 : [5]i32 = [16, 8, 4, 2, 1]; + +// ---- the font: 85 glyphs of seven rows ---- +pub const FONT57 : [595]u8 = [ + 0, 0, 0, 0, 0, 0, 0, // space + 4, 4, 4, 4, 4, 0, 4, // ! + 10, 10, 0, 0, 0, 0, 0, // " + 10, 10, 31, 10, 31, 10, 10, // # + 4, 15, 20, 14, 5, 30, 4, // $ + 24, 25, 2, 4, 8, 19, 3, // % + 12, 18, 20, 8, 21, 18, 13, // & + 4, 4, 0, 0, 0, 0, 0, // ' + 2, 4, 8, 8, 8, 4, 2, // ( + 8, 4, 2, 2, 2, 4, 8, // ) + 0, 4, 21, 14, 21, 4, 0, // * + 0, 4, 4, 31, 4, 4, 0, // + + 0, 0, 0, 0, 12, 4, 8, // , + 0, 0, 0, 31, 0, 0, 0, // - + 0, 0, 0, 0, 0, 12, 12, // . + 0, 1, 2, 4, 8, 16, 0, // / + 14, 17, 19, 21, 25, 17, 14, // 0 + 4, 12, 4, 4, 4, 4, 14, // 1 + 14, 17, 1, 2, 4, 8, 31, // 2 + 31, 2, 4, 2, 1, 17, 14, // 3 + 2, 6, 10, 18, 31, 2, 2, // 4 + 31, 16, 30, 1, 1, 17, 14, // 5 + 6, 8, 16, 30, 17, 17, 14, // 6 + 31, 1, 2, 4, 8, 8, 8, // 7 + 14, 17, 17, 14, 17, 17, 14, // 8 + 14, 17, 17, 15, 1, 2, 12, // 9 + 0, 12, 12, 0, 12, 12, 0, // : + 0, 12, 12, 0, 12, 4, 8, // ; + 2, 4, 8, 16, 8, 4, 2, // < + 0, 0, 31, 0, 31, 0, 0, // = + 8, 4, 2, 1, 2, 4, 8, // > + 14, 17, 1, 2, 4, 0, 4, // ? + 14, 17, 1, 13, 21, 21, 14, // @ + 14, 17, 17, 17, 31, 17, 17, // A + 30, 17, 17, 30, 17, 17, 30, // B + 14, 17, 16, 16, 16, 17, 14, // C + 28, 18, 17, 17, 17, 18, 28, // D + 31, 16, 16, 30, 16, 16, 31, // E + 31, 16, 16, 30, 16, 16, 16, // F + 14, 17, 16, 23, 17, 17, 15, // G + 17, 17, 17, 31, 17, 17, 17, // H + 14, 4, 4, 4, 4, 4, 14, // I + 7, 2, 2, 2, 2, 18, 12, // J + 17, 18, 20, 24, 20, 18, 17, // K + 16, 16, 16, 16, 16, 16, 31, // L + 17, 27, 21, 21, 17, 17, 17, // M + 17, 17, 25, 21, 19, 17, 17, // N + 14, 17, 17, 17, 17, 17, 14, // O + 30, 17, 17, 30, 16, 16, 16, // P + 14, 17, 17, 17, 21, 18, 13, // Q + 30, 17, 17, 30, 20, 18, 17, // R + 15, 16, 16, 14, 1, 1, 30, // S + 31, 4, 4, 4, 4, 4, 4, // T + 17, 17, 17, 17, 17, 17, 14, // U + 17, 17, 17, 17, 17, 10, 4, // V + 17, 17, 17, 21, 21, 21, 10, // W + 17, 17, 10, 4, 10, 17, 17, // X + 17, 17, 17, 10, 4, 4, 4, // Y + 31, 1, 2, 4, 8, 16, 31, // Z + 14, 8, 8, 8, 8, 8, 14, // [ + 0, 16, 8, 4, 2, 1, 0, // backslash + 14, 2, 2, 2, 2, 2, 14, // ] + 4, 10, 17, 0, 0, 0, 0, // ^ + 0, 0, 0, 0, 0, 0, 31, // _ + 31, 16, 16, 30, 17, 17, 30, // 64 Be + 31, 16, 16, 16, 16, 16, 16, // 65 Ge + 6, 10, 10, 10, 10, 31, 17, // 66 De + 21, 21, 14, 4, 14, 21, 21, // 67 Zhe + 14, 17, 1, 6, 1, 17, 14, // 68 Ze + 17, 17, 19, 21, 25, 17, 17, // 69 I + 10, 4, 17, 19, 21, 25, 17, // 70 short I + 7, 9, 9, 9, 9, 9, 17, // 71 El + 31, 17, 17, 17, 17, 17, 17, // 72 Pe + 17, 17, 17, 15, 1, 17, 14, // 73 U + 4, 14, 21, 21, 21, 14, 4, // 74 Ef + 18, 18, 18, 18, 18, 31, 1, // 75 Tse + 17, 17, 17, 15, 1, 1, 1, // 76 Che + 21, 21, 21, 21, 21, 21, 31, // 77 Sha + 21, 21, 21, 21, 21, 31, 1, // 78 Shcha + 24, 8, 8, 14, 9, 9, 14, // 79 hard sign + 17, 17, 17, 29, 19, 19, 29, // 80 Yeru + 16, 16, 16, 30, 17, 17, 30, // 81 soft sign + 14, 17, 1, 7, 1, 17, 14, // 82 E + 18, 21, 21, 29, 21, 21, 18, // 83 Yu + 15, 17, 17, 15, 5, 9, 17, // 84 Ya +]; + +// The glyph of Cyrillic capital U+0410 + k, A..Ya. Below 64 a Latin look-alike, else its own. +pub const CYR_MAP : [32]u8 = [ + 33, 64, 34, 65, 66, 37, 67, 68, // A Be Ve Ge De Ie Zhe Ze + 69, 70, 43, 71, 45, 40, 47, 72, // I shortI Ka El Em En O Pe + 48, 35, 52, 73, 74, 56, 75, 76, // Er Es Te U Ef Ha Tse Che + 77, 78, 79, 80, 81, 82, 83, 84, // Sha Shcha hard Yeru soft E Yu Ya +]; + +// ---- decode ---- + +fn byte_at(t: []const u8, i: i32) -> i32 { + return @as(i32, t[@as(usize, @intCast(i))]); +} +fn is_cont(b: i32) -> bool { + return b >= 128 && b < 192; +} + +// How many bytes the character at byte i takes: 1..4, or 1 for a malformed byte; 0 past the end. +pub fn cp_len(t: []const u8, i: i32) -> i32 { + const n = @as(i32, @intCast(t.len)); + if (i < 0 || i >= n) { return 0; } + const b = byte_at(t, i); + var k : i32 = 1; + if (b >= 248) { + k = 1; + } else if (b >= 240) { + k = 4; + } else if (b >= 224) { + k = 3; + } else if (b >= 192) { + k = 2; + } + if (i + k > n) { return 1; } + var j : i32 = 1; + while (j < k) { + if (!is_cont(byte_at(t, i + j))) { return 1; } + j = j + 1; + } + return k; +} + +// The code point at byte i; BAD for a malformed byte, 0 past the end. +pub fn cp_at(t: []const u8, i: i32) -> i32 { + const k = cp_len(t, i); + if (k == 0) { return 0; } + const b = byte_at(t, i); + if (k == 1) { + if (b < 128) { return b; } + return BAD; + } + const c1 = byte_at(t, i + 1) - 128; + if (k == 2) { return (b - 192) * 64 + c1; } + const c2 = byte_at(t, i + 2) - 128; + if (k == 3) { return (b - 224) * 4096 + c1 * 64 + c2; } + const c3 = byte_at(t, i + 3) - 128; + return (b - 240) * 262144 + c1 * 4096 + c2 * 64 + c3; +} + +// The number of characters in bytes a..b of t (a walk from a, by cp_len). +pub fn cp_count(t: []const u8, a: i32, b: i32) -> i32 { + var i : i32 = a; + var n : i32 = 0; + while (i < b) { + const k = cp_len(t, i); + if (k == 0) { return n; } + i = i + k; + n = n + 1; + } + return n; +} + +// ---- fold and glyph ---- + +// Upper case: Latin a..z, Cyrillic a..ya (U+0430..U+044F) and yo (U+0451). +pub fn fold(c: i32) -> i32 { + if (c >= 97 && c <= 122) { return c - 32; } + if (c >= 1072 && c <= 1103) { return c - 32; } + if (c == 1105) { return CYR_YO; } + return c; +} + +// The glyph that draws code point c; '?' for one the font does not have. +pub fn glyph57_of(c: i32) -> i32 { + const f = fold(c); + if (f >= 32 && f <= 95) { return f - 32; } + if (f >= CYR_A && f < CYR_A + 32) { return @as(i32, CYR_MAP[@as(usize, @intCast(f - CYR_A))]); } + if (f == CYR_YO) { return 37; } + return QMARK; +} + +// Whether pixel (gx, gy) of glyph g is lit, gx 0..4 and gy 0..6. +pub fn glyph57_px(g: i32, gx: i32, gy: i32) -> bool { + if (g < 0 || g >= GLYPHS57) { return false; } + if (gx < 0 || gx >= GW || gy < 0 || gy >= GH) { return false; } + const row = @as(i32, FONT57[@as(usize, @intCast(g * GH + gy))]); + return fmod(@divTrunc(row, PW5[@as(usize, @intCast(gx))]), 2) == 1; +} + +// Whether pixel (gx, gy) of code point c is lit. +pub fn glyph57_on(c: i32, gx: i32, gy: i32) -> bool { + return glyph57_px(glyph57_of(c), gx, gy); +} + +// The number of lit pixels of glyph g. +pub fn glyph57_lit(g: i32) -> i32 { + var n : i32 = 0; + var gy : i32 = 0; + while (gy < GH) { + var gx : i32 = 0; + while (gx < GW) { + if (glyph57_px(g, gx, gy)) { n = n + 1; } + gx = gx + 1; + } + gy = gy + 1; + } + return n; +} + +fn same_glyph(a: i32, b: i32) -> bool { + var gy : i32 = 0; + while (gy < GH) { + if (FONT57[@as(usize, @intCast(a * GH + gy))] != FONT57[@as(usize, @intCast(b * GH + gy))]) { return false; } + gy = gy + 1; + } + return true; +} + +// ---- tests ---- + +// "A", Zhe (D0 96), the euro sign (E2 82 AC); a stray continuation, "A", a cut-off lead; a lead +// byte followed by "A". +pub const U8_MIX : [6]u8 = [65, 208, 150, 226, 130, 172]; +pub const U8_BAD : [3]u8 = [150, 65, 208]; +pub const U8_CUT : [2]u8 = [208, 65]; + +test "UTF-8 decodes one, two and three bytes, and a malformed byte reads as one BAD character" { + const t = U8_MIX[0..6]; + assert(t.len == 6); + assert(cp_len(t, 0) == 1); + assert(cp_at(t, 0) == 65); + assert(cp_len(t, 1) == 2); + assert(cp_at(t, 1) == 1046); // Zhe, U+0416 + assert(cp_len(t, 3) == 3); + assert(cp_at(t, 3) == 8364); // the euro sign, U+20AC + assert(cp_count(t, 0, 6) == 3); + assert(cp_len(t, 6) == 0); + const s = U8_BAD[0..3]; + assert(cp_len(s, 0) == 1); + assert(cp_at(s, 0) == BAD); + assert(cp_at(s, 1) == 65); + assert(cp_len(s, 2) == 1); // a lead byte cut off by the end + assert(cp_at(s, 2) == BAD); + assert(cp_count(s, 0, 3) == 3); + const e = U8_CUT[0..2]; + assert(cp_len(e, 0) == 1); // a lead byte whose next byte is not a continuation + assert(cp_count(e, 0, 2) == 2); +} + +test "fold maps every lower-case letter to its capital and leaves the rest" { + assert(fold(97) == 65); + assert(fold(122) == 90); + assert(fold(1072) == 1040); + assert(fold(1103) == 1071); + assert(fold(1105) == CYR_YO); + assert(fold(1040) == 1040); + assert(fold(48) == 48); + assert(fold(124) == 124); +} + +test "the font table: 85 glyphs of seven 5-bit rows, a blank space, every other glyph lit" { + assert(GLYPHS57 * GH == 595); + var i : usize = 0; + while (i < 595) { + assert(@as(i32, FONT57[i]) <= 31); + i = i + 1; + } + assert(glyph57_lit(0) == 0); + var g : i32 = 1; + while (g < GLYPHS57) { + assert(glyph57_lit(g) > 0); + g = g + 1; + } + assert(glyph57_of(63) == QMARK); +} + +test "no two glyphs share a bitmap" { + var a : i32 = 0; + while (a < GLYPHS57) { + var b : i32 = a + 1; + while (b < GLYPHS57) { + assert(!same_glyph(a, b)); + b = b + 1; + } + a = a + 1; + } +} + +test "the Cyrillic map: 11 Latin look-alikes, and each of the 21 own glyphs used exactly once" { + var latin : i32 = 0; + var k : usize = 0; + while (k < 32) { + if (CYR_MAP[k] < 64) { latin = latin + 1; } + k = k + 1; + } + assert(latin == 11); + var g : i32 = 64; + while (g < GLYPHS57) { + var uses : i32 = 0; + var m : usize = 0; + while (m < 32) { + if (@as(i32, CYR_MAP[m]) == g) { uses = uses + 1; } + m = m + 1; + } + assert(uses == 1); + g = g + 1; + } + assert(glyph57_of(1040) == glyph57_of(65)); // A + assert(glyph57_of(1056) == glyph57_of(80)); // Er draws as P + assert(glyph57_of(1061) == glyph57_of(88)); // Ha draws as X + assert(glyph57_of(CYR_YO) == glyph57_of(69)); // Yo draws as E +} + +test "a lower-case letter lights exactly the pixels of its capital, Latin and Cyrillic" { + var c : i32 = 0; + while (c < 32) { + var gy : i32 = 0; + while (gy < GH) { + var gx : i32 = 0; + while (gx < GW) { + assert(glyph57_on(1072 + c, gx, gy) == glyph57_on(1040 + c, gx, gy)); + if (c < 26) { + assert(glyph57_on(97 + c, gx, gy) == glyph57_on(65 + c, gx, gy)); + } + gx = gx + 1; + } + gy = gy + 1; + } + c = c + 1; + } +} + +test "an unknown code point draws as a question mark, and pixels outside the box are dark" { + assert(glyph57_of(19968) == QMARK); // U+4E00 + assert(glyph57_of(BAD) == QMARK); + assert(glyph57_of(126) == QMARK); // '~' is not in the font + assert(!glyph57_on(65, 5, 0)); + assert(!glyph57_on(65, 0, 7)); + assert(!glyph57_on(65, 0 - 1, 0)); + assert(glyph57_on(65, 0, 6)); // A's left leg reaches the base line +} diff --git a/specs/t27f/idea_factory.t27 b/specs/t27f/idea_factory.t27 index 8affe44e9f..5c05863743 100644 --- a/specs/t27f/idea_factory.t27 +++ b/specs/t27f/idea_factory.t27 @@ -9,10 +9,10 @@ // was a hand-built module with its own f64 constants (token_floor, reel_particles, lesson_reel). // // THE FACTORY, in four stages, each a pure function the tests pin: -// wrap the idea is cut into lines of at most MAXC characters (greedy, by words; a longer -// word is split) and lines into scenes of at most MAXL; an explicit `|` cuts a scene. -// layout a scene's lines are set in the 3x5 font of scene_raster, centred, at the largest cell -// size S that fits the text box; every lit font pixel is a cell, a target for particles. +// wrap the idea, UTF-8, is cut into lines of at most MAXC characters (greedy, by words; +// a longer word is split) and lines into scenes of at most MAXL; a `|` cuts a scene. +// layout a scene's lines are set in glyph57's 5x7 Latin and Cyrillic font, centred, at the +// largest cell size S that fits the text box; every lit font pixel is a cell, a target. // assign at each scene cut every particle gets a new target: a sub-slot of a cell, or -- when // the preset has a card -- a point on the card's border around the text. The closing // scene is the address, CTA. @@ -23,13 +23,21 @@ // THE NUMBERS: layout and assignment run once per scene cut and may divide by a variable (the cell // size); the per-step and per-pixel paths are particle_ds's -- integer, Q8, table lookups. // -// NOT CLAIMED: the font is ASCII (no Cyrillic); no voice or music (lesson_reel mode 5 has those); -// gen-verilog has not been run on this module and no board has drawn it. +// A RUSSIAN IDEA is a u8 array of UTF-8 bytes (L3 keeps sources ASCII, and t27c keeps "\xNN" as +// four characters): RU_IDEA below, passed as RU_IDEA[0..80]. +// +// 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 module idea_factory; -use t27f::scene_raster::glyph_on; +use t27f::glyph57::glyph57_on; +use t27f::glyph57::cp_len; +use t27f::glyph57::cp_at; +use t27f::glyph57::cp_count; +use t27f::glyph57::GW; +use t27f::glyph57::GH; use t27f::scene_raster::rnd; use t27f::scene_raster::lcg; use t27f::scene_raster::clamp; @@ -71,6 +79,21 @@ pub const VERSION : u8 = 1; // ---- the factory's input: the lab recipe rewrites these two lines per render ---- pub const IDEA : str = "PARTICLES BUILD EVERY FRAME|ONE SPEC MANY STYLES"; pub const PRESET : usize = 0; + +// The Russian sample, "CHASTITSY STROYAT KADR|ODNA SPEKA MNOGO STILEJ" -- particles build the +// frame, one spec many styles -- as UTF-8 bytes; RU_IDEA[0..36] is its first scene alone. +pub const RU_IDEA : [80]u8 = [ + 208, 167, 208, 144, 208, 161, 208, 162, 208, 152, 208, 166, 208, 171, 32, + 208, 161, 208, 162, 208, 160, 208, 158, 208, 175, 208, 162, 32, + 208, 154, 208, 144, 208, 148, 208, 160, 124, + 208, 158, 208, 148, 208, 157, 208, 144, 32, + 208, 161, 208, 159, 208, 149, 208, 154, 208, 144, 32, + 208, 156, 208, 157, 208, 158, 208, 147, 208, 158, 32, + 208, 161, 208, 162, 208, 152, 208, 155, 208, 149, 208, 153 +]; +pub const RU_ZHA_UP : [4]u8 = [208, 150, 208, 144]; +pub const RU_ZHA_LO : [4]u8 = [208, 182, 208, 176]; +pub const RU_ZHZH : [4]u8 = [208, 150, 208, 150]; pub const CTA : str = "T27.AI"; // ---- the frame ---- @@ -86,16 +109,18 @@ pub const CY : i32 = 864; // 0.45 of the height, under the key // ---- the wrap and the layout ---- pub const MAXC : i32 = 9; // characters a line pub const MAXL : i32 = 4; // lines a scene -pub const LINE_PITCH : i32 = 7; // font pixels from one line's top to the next's +pub const ADV : i32 = 6; // font pixels from one letter's left to the next's +pub const LINE_PITCH : i32 = 9; // font pixels from one line's top to the next's pub const TEXT_W : i32 = 907; // 0.84 of the width pub const TEXT_H : i32 = 768; // 0.40 of the height pub const CELL_MAX : i32 = 40; // the largest font pixel, px -pub const MAXCELLS : i32 = 1024; // MAXL * MAXC * 15 = 540 is the most a scene can light +pub const MAXCELLS : i32 = 1280; // MAXL * MAXC * GW * GH = 1260 is the most a scene can light // ---- wrap ---- -// The k-th line of text t: what 0 = its first byte, 1 = its length in bytes, 2 = its scene; -1 when -// there is no such line. what 3 = the number of lines, what 4 = the number of scenes (k unused). +// The k-th line of text t: what 0 = its first byte, 1 = its length in bytes, 2 = its scene, 5 = its +// length in characters; -1 when there is no such line. what 3 = the number of lines, what 4 = the +// number of scenes (k unused). Widths are counted in characters, positions in bytes. pub fn wrap_walk(t: []const u8, k: i32, what: i32) -> i32 { const n = @as(i32, @intCast(t.len)); var line : i32 = 0; @@ -121,10 +146,12 @@ pub fn wrap_walk(t: []const u8, k: i32, what: i32) -> i32 { i = i + 1; } else { var j : i32 = i; - while (j < n && j - i < MAXC && t[@as(usize, @intCast(j))] != 32 && t[@as(usize, @intCast(j))] != 124) { - j = j + 1; + var w : i32 = 0; + while (j < n && w < MAXC && t[@as(usize, @intCast(j))] != 32 && t[@as(usize, @intCast(j))] != 124) { + j = j + cp_len(t, j); + w = w + 1; } - if (ls >= 0 && j - ls > MAXC) { + if (ls >= 0 && cp_count(t, ls, j) > MAXC) { flush = true; } else { if (ls < 0) { ls = i; } @@ -138,6 +165,7 @@ pub fn wrap_walk(t: []const u8, k: i32, what: i32) -> i32 { if (what == 0) { return ls; } if (what == 1) { return le - ls; } if (what == 2) { return scene; } + if (what == 5) { return cp_count(t, ls, le); } } line = line + 1; in_scene = in_scene + 1; @@ -181,12 +209,12 @@ pub fn scene_cell(t: []const u8, s: i32, c: i32, what: i32) -> i32 { if (wrap_walk(t, k, 2) == s) { if (first < 0) { first = k; } nl = nl + 1; - maxlen = imax(maxlen, wrap_walk(t, k, 1)); + maxlen = imax(maxlen, wrap_walk(t, k, 5)); } k = k + 1; } if (nl == 0) { return 0; } - const cols = maxlen * 4 - 1; + const cols = maxlen * ADV - 1; const rows = nl * LINE_PITCH - 2; const cs = imin(imin(@divTrunc(TEXT_W, cols), @divTrunc(TEXT_H, rows)), CELL_MAX); const ox = CX - @divTrunc(cols * cs, 2); @@ -199,19 +227,19 @@ pub fn scene_cell(t: []const u8, s: i32, c: i32, what: i32) -> i32 { var n : i32 = 0; var li : i32 = 0; while (li < nl) { - const st = wrap_walk(t, first + li, 0); - const ln = wrap_walk(t, first + li, 1); - const lox = CX - @divTrunc((ln * 4 - 1) * cs, 2); + var bi : i32 = wrap_walk(t, first + li, 0); + const ln = wrap_walk(t, first + li, 5); + const lox = CX - @divTrunc((ln * ADV - 1) * cs, 2); var ci : i32 = 0; while (ci < ln) { - const ch = @as(i32, t[@as(usize, @intCast(st + ci))]); + const ch = cp_at(t, bi); var gy : i32 = 0; - while (gy < 5) { + while (gy < GH) { var gx : i32 = 0; - while (gx < 3) { - if (glyph_on(ch, gx, gy)) { + while (gx < GW) { + if (glyph57_on(ch, gx, gy)) { if (n == c) { - if (what == 0) { return lox + (ci * 4 + gx) * cs + @divTrunc(cs, 2); } + if (what == 0) { return lox + (ci * ADV + gx) * cs + @divTrunc(cs, 2); } if (what == 1) { return oy + (li * LINE_PITCH + gy) * cs + @divTrunc(cs, 2); } } n = n + 1; @@ -220,6 +248,7 @@ pub fn scene_cell(t: []const u8, s: i32, c: i32, what: i32) -> i32 { } gy = gy + 1; } + bi = bi + cp_len(t, bi); ci = ci + 1; } li = li + 1; @@ -291,8 +320,8 @@ pub fn main() -> void { var cr : [3072]i32 = undefined; var cg : [3072]i32 = undefined; var cb : [3072]i32 = undefined; - var cellx : [1024]i32 = undefined; - var celly : [1024]i32 = undefined; + var cellx : [1280]i32 = undefined; + var celly : [1280]i32 = undefined; var kt : [1024]i32 = undefined; var tt : [1024]i32 = undefined; var accr : [1080]i32 = undefined; @@ -514,6 +543,48 @@ test "cells are the font's lit pixels: AA lights twice A, a space lights none" { assert(scene_cell("a", 0, 0, 2) == a); } +// "CHASTITSY STROYAT KADR" -- particles build the frame -- in Cyrillic capitals, UTF-8. +test "a Russian idea wraps by letters, not bytes: a 7-letter, 14-byte word keeps one line" { + const t = RU_IDEA[0..36]; + assert(t.len == 36); + assert(line_count(t) == 3); + assert(wrap_walk(t, 0, 5) == 7); + assert(wrap_walk(t, 0, 1) == 14); + assert(wrap_walk(t, 1, 5) == 6); + assert(wrap_walk(t, 2, 0) == 28); + assert(wrap_walk(t, 2, 5) == 4); + assert(scene_count(t) == 1); + const r = RU_IDEA[0..80]; + assert(line_count(r) == 7); + assert(scene_count(r) == 2); + assert(wrap_walk(r, 3, 5) == 4); // ODNA opens scene 1 + assert(wrap_walk(r, 6, 5) == 6); // STILEJ, 12 bytes + assert(wrap_walk(r, 6, 1) == 12); + const n = scene_cell(t, 0, 0, 2); + assert(n > 0 && n <= MAXCELLS); + var c : i32 = 0; + while (c < n) { + const cx = scene_cell(t, 0, c, 0); + assert(cx > scene_cell(t, 0, 0, 3) && cx < scene_cell(t, 0, 0, 5)); + c = c + 1; + } +} + +test "Russian lower case lights the cells of its capitals, and ZheZhe lights twice Zhe" { + const up = RU_ZHA_UP[0..4]; + const lo = RU_ZHA_LO[0..4]; + const n = scene_cell(up, 0, 0, 2); + assert(n > 0); + assert(scene_cell(lo, 0, 0, 2) == n); + var c : i32 = 0; + while (c < n) { + assert(scene_cell(lo, 0, c, 0) == scene_cell(up, 0, c, 0)); + assert(scene_cell(lo, 0, c, 1) == scene_cell(up, 0, c, 1)); + c = c + 1; + } + assert(scene_cell(RU_ZHZH[0..4], 0, 0, 2) == 2 * scene_cell(RU_ZHZH[0..2], 0, 0, 2)); +} + test "every target of every scene lies inside the frame, and the card frames the text" { var s : i32 = 0; while (s < reel_scenes(IDEA)) { From 9ec9e295f749c5be998e1eaab6fe728737b6df8c Mon Sep 17 00:00:00 2001 From: Dmitrii Vasilev Date: Mon, 19 Oct 2026 09:54:06 +0700 Subject: [PATCH 04/21] t27f skill: a new spec's t27b ledger row comes from the arm64 run, keep the PR a draft until then (Refs #10788) Co-Authored-By: Claude Opus 5.5 --- .claude/skills/t27f/SKILL.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.claude/skills/t27f/SKILL.md b/.claude/skills/t27f/SKILL.md index f0920dfa51..30e03776a4 100644 --- a/.claude/skills/t27f/SKILL.md +++ b/.claude/skills/t27f/SKILL.md @@ -167,6 +167,7 @@ Traps from this tick (ffmpeg on the lab): - lavfi rejects `d=.900`. Print the duration as `$((m/1000)).$(printf %03d $((m%1000)))`. - `pkill -f` on the loop script leaves its child renders running. Find them with `ps -eo pid,args | grep '[s]h xf'` and kill them by pid. - Through `fs-lab.sh`, `pkill -f 'why_seal'` also matches the ssh command line that carries it and kills your own session; the output comes back empty. Bracket the first letter (`pkill -f '[w]hy_seal'`) so the pattern does not match itself. A killed session leaves the detached ratchets it started running, so several end up writing one log; list them with `ps -eo pid,etimes,args | grep '[c]orpus-only'` before you read a verdict. +- **A new spec needs its own row in `docs/reports/t27b_expectations.json`**, or `t27b-native-ratchet` fails with UNLISTED. The issue gate still auto-merges the PR red: #10784 landed that way and left five unlisted specs for #10792 to carry. The lab is x86_64, and `t27b test` there answers "the t27b JIT runs only on arm64", so the lab cannot give you the verdict. Take it from the PR's own arm64 run, which prints each UNLISTED path with its verdict. Keep the PR a draft until that row is in. Never guess "pass": a `[N]T` or `ExprCall` blocker turns a guessed row into a regression. **Holding the address when the voice runs long (t27#10629).** If an overlay must stay on screen for a minimum time and must also stay off a crossfade, then the crossfade has to move; the overlay cannot. Work out in frames the latest cut that still leaves the last scene its minimum: cut 1 is capped at `BEAT_CUT1_LAST_MS` = 13249. Prove it is the latest with a one-ms-later control. Only the voices the reel speeds up are affected, so the `cmp` diff is limited to those voices and to crossfade 1's offset. The cost is real: the crossfade starts up to 184 ms under the task's last word, and lesson 1's measured voice (1300/4200/6800) is one of the affected voices. Write a test that bounds that overlap. From dae06a9e30e232823c1a0bfe87f8985705b8821b Mon Sep 17 00:00:00 2001 From: Dmitrii Vasilev Date: Mon, 19 Oct 2026 09:57:28 +0700 Subject: [PATCH 05/21] Ledger: glyph57 and petal_menu pass the t27b front end (Refs #10788) On the x86_64 lab, the t27b front-end check (--check) prints every blocker before it stops at "the t27b JIT runs only on arm64". glyph57.t27 and petal_menu.t27 (new on master from #10786, also unlisted) reach that line with no blocker. The control, frame_shard.t27, prints ExprCall(rejected fn). Both rows are pass; this branch's arm64 run confirms them. max_not_pass stays 138. The t27f skill's ratchet note said the lab cannot give a verdict. That was wrong; it now gives the --check method and its control. Co-Authored-By: Claude Opus 5.5 --- .claude/skills/t27f/SKILL.md | 2 +- docs/reports/t27b_expectations.json | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.claude/skills/t27f/SKILL.md b/.claude/skills/t27f/SKILL.md index 30e03776a4..4b7f03aacc 100644 --- a/.claude/skills/t27f/SKILL.md +++ b/.claude/skills/t27f/SKILL.md @@ -167,7 +167,7 @@ Traps from this tick (ffmpeg on the lab): - lavfi rejects `d=.900`. Print the duration as `$((m/1000)).$(printf %03d $((m%1000)))`. - `pkill -f` on the loop script leaves its child renders running. Find them with `ps -eo pid,args | grep '[s]h xf'` and kill them by pid. - Through `fs-lab.sh`, `pkill -f 'why_seal'` also matches the ssh command line that carries it and kills your own session; the output comes back empty. Bracket the first letter (`pkill -f '[w]hy_seal'`) so the pattern does not match itself. A killed session leaves the detached ratchets it started running, so several end up writing one log; list them with `ps -eo pid,etimes,args | grep '[c]orpus-only'` before you read a verdict. -- **A new spec needs its own row in `docs/reports/t27b_expectations.json`**, or `t27b-native-ratchet` fails with UNLISTED. The issue gate still auto-merges the PR red: #10784 landed that way and left five unlisted specs for #10792 to carry. The lab is x86_64, and `t27b test` there answers "the t27b JIT runs only on arm64", so the lab cannot give you the verdict. Take it from the PR's own arm64 run, which prints each UNLISTED path with its verdict. Keep the PR a draft until that row is in. Never guess "pass": a `[N]T` or `ExprCall` blocker turns a guessed row into a regression. +- **A new spec needs its own row in `docs/reports/t27b_expectations.json`**, or `t27b-native-ratchet` fails with UNLISTED. The issue gate still auto-merges the PR red: #10784 landed that way and left five unlisted specs for #10792 to carry. The lab is x86_64, and `t27b test` there ends with "the t27b JIT runs only on arm64". The front end still runs first: `t27b test --check` prints every blocker before that line. Run a blocked control next to your spec; `specs/t27f/frame_shard.t27` prints `unsupported construct ExprCall(rejected fn)`. If your spec reaches the JIT line with no blocker, and `t27c test-report` passes it, write a pass row. The PR's own arm64 run confirms or refutes the row. Keep the PR a draft until the row is in. Never guess "pass" without that check: a `[N]T` or `ExprCall` blocker turns a guessed row into a regression. Measured on glyph57, idea_factory and petal_menu (#10792). **Holding the address when the voice runs long (t27#10629).** If an overlay must stay on screen for a minimum time and must also stay off a crossfade, then the crossfade has to move; the overlay cannot. Work out in frames the latest cut that still leaves the last scene its minimum: cut 1 is capped at `BEAT_CUT1_LAST_MS` = 13249. Prove it is the latest with a one-ms-later control. Only the voices the reel speeds up are affected, so the `cmp` diff is limited to those voices and to crossfade 1's offset. The cost is real: the crossfade starts up to 184 ms under the task's last word, and lesson 1's measured voice (1300/4200/6800) is one of the affected voices. Write a test that bounds that overlap. diff --git a/docs/reports/t27b_expectations.json b/docs/reports/t27b_expectations.json index 65f2dee281..a7ffad481b 100644 --- a/docs/reports/t27b_expectations.json +++ b/docs/reports/t27b_expectations.json @@ -1502,6 +1502,7 @@ {"path": "specs/t27f/frame_cache.t27", "t27b": "pass"}, {"path": "specs/t27f/frame_shard.t27", "t27b": "blocked", "blocker": "type [N]T", "reason": "unimplemented"}, {"path": "specs/t27f/frontend.t27", "t27b": "pass"}, + {"path": "specs/t27f/glyph57.t27", "t27b": "pass"}, {"path": "specs/t27f/hot.t27", "t27b": "pass"}, {"path": "specs/t27f/idea_factory.t27", "t27b": "pass"}, {"path": "specs/t27f/lesson_reel.t27", "t27b": "blocked", "blocker": "type [N]T", "reason": "unimplemented"}, @@ -1510,6 +1511,7 @@ {"path": "specs/t27f/particle_engine.t27", "t27b": "blocked", "blocker": "ExprCall(@sin)", "reason": "unimplemented"}, {"path": "specs/t27f/particle_field.t27", "t27b": "pass"}, {"path": "specs/t27f/pbr_sphere.t27", "t27b": "pass"}, + {"path": "specs/t27f/petal_menu.t27", "t27b": "pass"}, {"path": "specs/t27f/quest.t27", "t27b": "pass"}, {"path": "specs/t27f/reel.t27", "t27b": "pass"}, {"path": "specs/t27f/reel_beats.t27", "t27b": "pass"}, From 29f8511624a89b14f67a7fc949fb91a32b72d849 Mon Sep 17 00:00:00 2001 From: Dmitrii Vasilev Date: Mon, 19 Oct 2026 10:11:22 +0700 Subject: [PATCH 06/21] t27f: idea factory picks its style preset from a hash of the idea, PRESET_AUTO (Closes #10803) A batch of ideas rendered with the factory all came out in PRESET 0 unless someone edited the constant by hand. Now PRESET = PRESET_AUTO (255) makes main run pick_preset(IDEA, PRESET). That uses preset_of(IDEA), which is FNV-1a 32 over the idea's bytes (UTF-8 for Russian) mod PRESETS, so the same idea always gets the same style and different ideas spread over the five. Any other PRESET is kept as given, and PRESET stays 0, so today's render is unchanged. This is Remotion's random(seed), applied to style. Railway lab: t27c test-report 13/13 pass, 0 vacuous. The fnv1a32 test checks the published FNV-1a 32 vectors ("", a, b, foobar). Across the one-letter ideas A..Z every preset is picked. 5 of 5 mutants are killed: basis, prime, FNV-1 order, AUTO -> 0, mod 4. t27b raises no blocker, so the pass row stands. gen emits pick_preset. Seal re-saved, all hashes MATCH. Co-Authored-By: Claude Opus 5.5 --- .claude/skills/t27f/SKILL.md | 6 +++ .trinity/seals/t27f_idea_factory.json | 16 +++--- specs/t27f/idea_factory.t27 | 73 ++++++++++++++++++++++++++- 3 files changed, 86 insertions(+), 9 deletions(-) diff --git a/.claude/skills/t27f/SKILL.md b/.claude/skills/t27f/SKILL.md index 4b7f03aacc..553d99ae99 100644 --- a/.claude/skills/t27f/SKILL.md +++ b/.claude/skills/t27f/SKILL.md @@ -408,6 +408,12 @@ of particles, and runnable in the OS itself and on FPGA. Two specs do this: - **`specs/t27f/idea_factory.t27` is the factory**, `(IDEA, PRESET) -> RGB24 frames`. It wraps (at most 9 characters per line, 4 lines per scene, `|` cuts a scene), lays out in glyph57's 5x7 font, assigns particles to glyph sub-slots and the card border, and closes on `CTA`. +- **A batch of ideas: set `PRESET = PRESET_AUTO`** (255). `main` runs `pick_preset(IDEA, PRESET)`, + and AUTO means `preset_of(IDEA)` = FNV-1a 32 of the idea's bytes mod `PRESETS`. The same idea + always gets the same style, and different ideas spread across all five; A..Z hit every preset. + This is Remotion's `random(seed)` applied to style. Any other `PRESET` value is used as given + (gHashTag/t27#10803). Test hashes against the published FNV vectors (`""`, `a`, `b`, `foobar`), + never against a value the spec computes itself. **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 4f321c9bb3..6088bab421 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:6a6ffcf30791e08b93221c1067abfc7e053f13c23dff90de4d9f7c9e23461479", - "gen_hash_rust": "sha256:d64150e24319f74e2b9413d45cfbd96a7d8bbc0df9d51c9fac0b68c8837b2efb", - "gen_hash_verilog": "sha256:387961ee44ce4ce7f32d4ccb23a4ac58ac6ef688db3b4c8aef38d1c60e41cc18", - "gen_hash_zig": "sha256:115d3f8ff262b6fd65b59d760aeb7eff66500b9f77f56577fb22233fefaa65e7", + "gen_hash_c": "sha256:ddc41e93c87c1268b2d075c6b34fba3208ac77bc2db7992ee9430b219f3aa2f1", + "gen_hash_rust": "sha256:dfb079933e8ffe3343e2360a23e71f9a92bc7c27b20a2e11c6659f95e1562c63", + "gen_hash_verilog": "sha256:029af4d7206071497f23fd5b12d15eb47df379d6f508d2f9a192edfb12337578", + "gen_hash_zig": "sha256:27984b39231077b1f334e7d6fe2f2249667ffb6838f0bf21f4b8e38861e98fd7", "module": "idea_factory", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-19T02:41:09Z", + "sealed_at": "2026-10-19T03:10:38Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:249909fbd1679b488be14be323c0079735a55895560423934c21daf28c824db9", + "spec_hash": "sha256:26ab3c6cae74c0c62124efb321098e184703f4b05f1e53fc902fa209dd036768", "spec_path": "specs/t27f/idea_factory.t27", "tests": { "failed": 0, "failing": [], "forced": false, - "passed": 10, - "total": 10, + "passed": 13, + "total": 13, "vacuous": 0 }, "toolchain": { diff --git a/specs/t27f/idea_factory.t27 b/specs/t27f/idea_factory.t27 index 5c05863743..ca8b8f6ab8 100644 --- a/specs/t27f/idea_factory.t27 +++ b/specs/t27f/idea_factory.t27 @@ -49,6 +49,7 @@ use t27f::scene_raster::red; use t27f::scene_raster::green; use t27f::scene_raster::blue; use t27f::particle_ds::ONE; +use t27f::particle_ds::PRESETS; use t27f::particle_ds::BG; use t27f::particle_ds::KEY_COL; use t27f::particle_ds::GRAIN; @@ -272,6 +273,32 @@ pub fn frame_bytes() -> i32 { return FRAME_W * FRAME_H * 3; } +// ---- style: the preset can come from the idea itself (gHashTag/t27#10803) ---- + +// PRESET = PRESET_AUTO asks the factory to pick the style from the idea's bytes, so a batch of +// ideas spreads over the presets and a re-render of one idea keeps its style. +pub const PRESET_AUTO : usize = 255; +pub const FNV32_BASIS : u32 = 2166136261; +pub const FNV32_PRIME : u32 = 16777619; + +// FNV-1a 32 over the bytes of t (UTF-8 for a Russian idea): xor the byte in, then multiply. +pub fn fnv1a32(t: []const u8) -> u32 { + var h : u32 = FNV32_BASIS; + var i : usize = 0; + while (i < t.len) { + h = (h ^ @as(u32, t[i])) *% FNV32_PRIME; + i = i + 1; + } + return h; +} +pub fn preset_of(t: []const u8) -> usize { + return @as(usize, fnv1a32(t) % @as(u32, PRESETS)); +} +pub fn pick_preset(t: []const u8, p: usize) -> usize { + if (p == PRESET_AUTO) { return preset_of(t); } + return p; +} + // ---- assign ---- // Where card particle m of mm sits on the border of the rectangle (x0, y0)-(x1, y1): what 0 = x, @@ -329,7 +356,7 @@ pub fn main() -> void { var accb : [1080]i32 = undefined; var row : [3240]u8 = undefined; - const p = PRESET; + const p = pick_preset(IDEA, PRESET); const rr = reach(p); var d2 : i32 = 0; while (d2 <= rr * rr) { @@ -633,3 +660,47 @@ test "a frame is 1080x1920 RGB24, 6220800 bytes" { assert(frame_bytes() == 6220800); assert(FPS == 30); } + +// Expected values are the published FNV-1a 32 test vectors, not values this file computes. +test "fnv1a32 gives the published FNV-1a 32 vectors for the empty string, a, b and foobar" { + assert(fnv1a32("") == 2166136261); + assert(fnv1a32("a") == 3826002220); + assert(fnv1a32("b") == 3876335077); + assert(fnv1a32("foobar") == 3214735720); +} + +test "PRESET_AUTO picks the preset from the idea's hash, any other preset is kept" { + assert(PRESET == 0); + assert(PRESET_AUTO >= PRESETS); + assert(preset_of("a") == 0); + assert(preset_of("b") == 2); + assert(pick_preset("b", PRESET_AUTO) == 2); + assert(pick_preset("b", 3) == 3); + assert(pick_preset("a", 4) == 4); + assert(pick_preset(IDEA, PRESET_AUTO) == preset_of(IDEA)); + assert(preset_of(RU_IDEA[0..80]) < PRESETS); +} + +test "the 26 one-letter ideas A to Z between them pick every preset" { + var hit : [5]i32 = undefined; + var k : usize = 0; + while (k < 5) { + hit[k] = 0; + k = k + 1; + } + var b : [1]u8 = undefined; + var c : u8 = 65; + while (c <= 90) { + b[0] = c; + const p = preset_of(b[0..1]); + assert(p < PRESETS); + hit[p] = hit[p] + 1; + c = c + 1; + } + assert(hit[0] > 0); + assert(hit[1] > 0); + assert(hit[2] > 0); + assert(hit[3] > 0); + assert(hit[4] > 0); + assert(hit[0] + hit[1] + hit[2] + hit[3] + hit[4] == 26); +} From 7f33764b49ba10c596f9b204762059db2f220dee Mon Sep 17 00:00:00 2001 From: Dmitrii Vasilev Date: Mon, 19 Oct 2026 10:16:31 +0700 Subject: [PATCH 07/21] Ledger rows for mos_psp103 and re-sealed frontend, both inherited from master (Refs #10788) Two of the three red checks on this PR came from master, not from the glyph57 change. specs/analog/mos_psp103.t27 and mos_psp103_data.t27 landed on master without t27b ledger rows, so the ratchet reported them UNLISTED. They are added in sorted position as pass. The t27b front end on the lab (--check) reports 0 unsupported constructs for both, matching the pass the CI run measured. frontend.t27 does not import glyph57, but a module it uses moved on master after its seal, so all four gen hashes were stale. It is re-sealed on the lab against this branch's tree, and --verify reports all hashes MATCH. The coverage gate failed on the same seal. Co-Authored-By: Claude Opus 5.5 --- .trinity/seals/t27f_T27fFrontend.json | 20 ++++++++++---------- docs/reports/t27b_expectations.json | 2 ++ 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/.trinity/seals/t27f_T27fFrontend.json b/.trinity/seals/t27f_T27fFrontend.json index d269ef04b3..eb42cde2aa 100644 --- a/.trinity/seals/t27f_T27fFrontend.json +++ b/.trinity/seals/t27f_T27fFrontend.json @@ -1,5 +1,5 @@ { - "built_by": "t27c-bootstrap@0.5.2+9448ab4be", + "built_by": "t27c-bootstrap@0.5.2+355d44a39", "closure": [ { "spec": "specs/t27f/reel.t27", @@ -15,27 +15,27 @@ } ], "config": "gen=c,rust,verilog,zig;tests=zig", - "gen_hash_c": "sha256:cb17d2d0955860474a7e522538691fe2efe5b3bf539497a5405349e2b830ee39", - "gen_hash_rust": "sha256:4b7f761fc91186cf4c9a8abb2a7c503407ddbeba06cbc34c7af51c811e1bde95", - "gen_hash_verilog": "sha256:7a71807e11de0cf1e85ccbf6fbe09c70ca7f8a84db78e7548eb9699ca3576777", - "gen_hash_zig": "sha256:bf142bb8a420fd242fc209833cb874d58a6ffffe23e6ea3121a4cc5cb1d16e9e", + "gen_hash_c": "sha256:0fd5ffd52dde4182ccbb30ee2ab37504725ce18fe33b89c613424f7149a7b4dd", + "gen_hash_rust": "sha256:e1cf0477a2e4e7ecd3c4d75ab3e31062d5aa0348266cb78146c6fcea93b8e9cd", + "gen_hash_verilog": "sha256:80c2a11a9133eb38e2361b0b38539c8cd8672d4823961744e5b0a83930c12ec2", + "gen_hash_zig": "sha256:ee73aa033ff4acd310a4c79bcb4b11bdf80cc957a87b986510c651a6f0498a46", "module": "T27fFrontend", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-18T17:03:22Z", + "sealed_at": "2026-10-19T03:15:47Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:13cd46e9bdacfbae4f6ecd247f771ad6159166aa3a45cd56ce25b57be06de58c", + "spec_hash": "sha256:a3e5ec03af02e77f1412a95add4ee269c159ecd83209c7475222a4120dd97599", "spec_path": "specs/t27f/frontend.t27", "tests": { "failed": 0, "failing": [], "forced": false, - "passed": 7, - "total": 7, + "passed": 8, + "total": 8, "vacuous": 0 }, "toolchain": { - "test_runner": "sha256:1982ae948e24d00c5d221ccec12dfce1eb01204b53e37b0f857f93b16d64629d", + "test_runner": "sha256:8512b80da533b79289110fca4a65f3336abd7a059ca4b72ea595d5e33ff8ad10", "zig": "0.16.0" } } \ No newline at end of file diff --git a/docs/reports/t27b_expectations.json b/docs/reports/t27b_expectations.json index a7ffad481b..c292b13bab 100644 --- a/docs/reports/t27b_expectations.json +++ b/docs/reports/t27b_expectations.json @@ -46,6 +46,8 @@ {"path": "specs/analog/bench.t27", "t27b": "pass"}, {"path": "specs/analog/linear.t27", "t27b": "pass"}, {"path": "specs/analog/linear_zoh.t27", "t27b": "blocked", "blocker": "ExprCall(@sin)", "reason": "unimplemented"}, + {"path": "specs/analog/mos_psp103.t27", "t27b": "pass"}, + {"path": "specs/analog/mos_psp103_data.t27", "t27b": "pass"}, {"path": "specs/analog/mos_table.t27", "t27b": "pass"}, {"path": "specs/analog/sar_cdac.t27", "t27b": "pass"}, {"path": "specs/analog/sar_comparator.t27", "t27b": "pass"}, From 879ff99e22d1716428088c47335cd69ec4133b19 Mon Sep 17 00:00:00 2001 From: Dmitrii Vasilev Date: Mon, 19 Oct 2026 10:30:24 +0700 Subject: [PATCH 08/21] t27f: idea factory batch -- five ideas cover all five presets, and the compiled binary agrees with the spec (Closes #10815) PRESET_AUTO (#10806) was checked only in the t27c interpreter. A new test pins a batch of five ideas whose AUTO presets are noir, neon, trit, paper and rain. On the Railway lab each idea was built twice from the generated Zig, once with PRESET = PRESET_AUTO and once with the pinned preset written in, and the two frame streams have the same md5 for 5 of 5 ideas. Idea 1 with preset 1 gives a different stream, the control. So gen and the interpreter pick the same style. Lab: t27c test-report 14/14 pass, 0 vacuous. Mutants killed: a pinned preset swapped (paper -> rain) and one idea byte changed. The t27b front end reports 0 unsupported constructs. Seal re-saved, all hashes MATCH. The t27f skill records the recipe and the trap that cost one run: five parallel libx264 encoders failed to open, the tee closed, and md5 hashed truncated streams, so 3 of 5 read DIFF until md5 ran on its own. Co-Authored-By: Claude Opus 5.5 --- .claude/skills/t27f/SKILL.md | 13 +++++++++++++ .trinity/seals/t27f_idea_factory.json | 16 ++++++++-------- specs/t27f/idea_factory.t27 | 17 +++++++++++++++++ 3 files changed, 38 insertions(+), 8 deletions(-) diff --git a/.claude/skills/t27f/SKILL.md b/.claude/skills/t27f/SKILL.md index 553d99ae99..9d9e9d44f3 100644 --- a/.claude/skills/t27f/SKILL.md +++ b/.claude/skills/t27f/SKILL.md @@ -414,6 +414,19 @@ of particles, and runnable in the OS itself and on FPGA. Two specs do this: This is Remotion's `random(seed)` applied to style. Any other `PRESET` value is used as given (gHashTag/t27#10803). Test hashes against the published FNV vectors (`""`, `a`, `b`, `foobar`), never against a value the spec computes itself. +- **Prove AUTO on the compiled binary, not only in the interpreter.** For each idea, build it + twice on the lab: once with `PRESET = PRESET_AUTO` and once with the expected preset written in. + md5 the two frame streams (`./f 2>&1 1>fo.txt | md5sum`). They must be SAME, and the idea with a + wrong preset must be DIFF (the control). Then gen and the interpreter agree, and you need no image + analysis: noir, trit and rain backgrounds differ by a few levels, so a colour check is fragile. + The batch in the spec's test (#10815) is five ideas that cover all five presets; render it as the + batch preview. **To find an idea's preset without a print:** a scratch spec with + `assert((preset_of(X) & b) == b)` for b = 1, 2, 4 reads the value off which tests pass. + Build the variants in parallel with `&` and `wait`: 11 builds and renders take about as long as one. + **Trap: never `tee` the frame stream into md5 and ffmpeg at once while several encoders run.** + Five parallel libx264 encoders on the lab failed with "Error while opening encoder", the + pipe closed, and md5 hashed a truncated stream: 3 of 5 read DIFF. Run md5 separately, then + encode one at a time. A DIFF that coincides with an encoder error is the harness, not the code. **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 6088bab421..c0bf1f04d2 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:ddc41e93c87c1268b2d075c6b34fba3208ac77bc2db7992ee9430b219f3aa2f1", - "gen_hash_rust": "sha256:dfb079933e8ffe3343e2360a23e71f9a92bc7c27b20a2e11c6659f95e1562c63", - "gen_hash_verilog": "sha256:029af4d7206071497f23fd5b12d15eb47df379d6f508d2f9a192edfb12337578", - "gen_hash_zig": "sha256:27984b39231077b1f334e7d6fe2f2249667ffb6838f0bf21f4b8e38861e98fd7", + "gen_hash_c": "sha256:bb1ef6081add1e9fa249288e6ebd223bc66be7f13d91210eed6dac48ed4a01aa", + "gen_hash_rust": "sha256:2420620e8b22b844cf0912ec4a21d7358a0045cec3508d8a14c6d8206f169ccb", + "gen_hash_verilog": "sha256:8f8cd1625c7f3d516bc5e597c4860b81f2a4860ce6d6f3cb4b06c2578568a49d", + "gen_hash_zig": "sha256:b4136a42488a6715a58f8a2e2e482871209c94a0009a78cab7a1f357db42c0d2", "module": "idea_factory", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-19T03:10:38Z", + "sealed_at": "2026-10-19T03:28:21Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:26ab3c6cae74c0c62124efb321098e184703f4b05f1e53fc902fa209dd036768", + "spec_hash": "sha256:a68fbc93ea94b32e264ae91b82b149eb7530c3af4e2787c8b457a2eeec0c3482", "spec_path": "specs/t27f/idea_factory.t27", "tests": { "failed": 0, "failing": [], "forced": false, - "passed": 13, - "total": 13, + "passed": 14, + "total": 14, "vacuous": 0 }, "toolchain": { diff --git a/specs/t27f/idea_factory.t27 b/specs/t27f/idea_factory.t27 index ca8b8f6ab8..9ce8b39d95 100644 --- a/specs/t27f/idea_factory.t27 +++ b/specs/t27f/idea_factory.t27 @@ -50,6 +50,11 @@ use t27f::scene_raster::green; use t27f::scene_raster::blue; use t27f::particle_ds::ONE; use t27f::particle_ds::PRESETS; +use t27f::particle_ds::P_NOIR; +use t27f::particle_ds::P_NEON; +use t27f::particle_ds::P_TRIT; +use t27f::particle_ds::P_PAPER; +use t27f::particle_ds::P_RAIN; use t27f::particle_ds::BG; use t27f::particle_ds::KEY_COL; use t27f::particle_ds::GRAIN; @@ -704,3 +709,15 @@ test "the 26 one-letter ideas A to Z between them pick every preset" { assert(hit[4] > 0); assert(hit[0] + hit[1] + hit[2] + hit[3] + hit[4] == 26); } + +// ---- a batch: five ideas whose AUTO presets cover all five (gHashTag/t27#10815) ---- +// The lab built each idea twice, once with PRESET = PRESET_AUTO and once with the preset pinned +// here, and the two binaries' frame streams were byte-identical (md5), so the generated code +// picks what the interpreter picks. Idea 1 with preset 1 gave a different stream (the control). +test "a batch of five ideas covers all five presets under PRESET_AUTO" { + assert(preset_of("SPEC IS THE SOURCE|EVERY VIEW DERIVED") == P_NOIR); + assert(preset_of("ONE LAW|MANY SURFACES") == P_NEON); + assert(preset_of("THREE STATES|BEAT TWO") == P_TRIT); + assert(preset_of("THE FPGA RUNS|THE SPEC") == P_PAPER); + assert(preset_of("SEALED|EVERY HASH") == P_RAIN); +} From 4f739c161b854365b1adcbee011daf15ddb982b0 Mon Sep 17 00:00:00 2001 From: Dmitrii Vasilev Date: Mon, 19 Oct 2026 10:31:55 +0700 Subject: [PATCH 09/21] Ledger row for sar_switch, another master spec landed without one (Refs #10788) specs/analog/sar_switch.t27 (analog M4c) reached master without a t27b row, so this PR's ratchet reported it UNLISTED. The arm64 run on 7f33764b4 measured it as a t27b pass, so the row says pass. Master is merged in first so the row names a file this branch contains. Co-Authored-By: Claude Opus 5.5 --- docs/reports/t27b_expectations.json | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/reports/t27b_expectations.json b/docs/reports/t27b_expectations.json index 992b4c206c..5679efb8fa 100644 --- a/docs/reports/t27b_expectations.json +++ b/docs/reports/t27b_expectations.json @@ -50,6 +50,7 @@ {"path": "specs/analog/mos_psp103_data.t27", "t27b": "pass"}, {"path": "specs/analog/mos_table.t27", "t27b": "pass"}, {"path": "specs/analog/sar_cdac.t27", "t27b": "pass"}, + {"path": "specs/analog/sar_switch.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"}, From e9b55d645eea4276794a33523fad1a9ff9f7dd60 Mon Sep 17 00:00:00 2001 From: Dmitrii Vasilev Date: Mon, 19 Oct 2026 10:43:15 +0700 Subject: [PATCH 10/21] t27f: idea factory pre-flight -- refuse an idea the font cannot draw before writing a frame (Closes #10820) glyph57 draws a character it lacks as '?', so an idea with a phi, a euro sign or a brace used to ship with the wrong letter on screen. preflight(t) now returns PF_EMPTY (no scene), PF_GLYPH (missing_glyphs > 0; first_missing gives the byte offset), PF_LONG (reel past MAX_S = 60 s) or PF_OK, in that order, and main writes no frame unless it is PF_OK. A real '?', a '|' cut and a space do not count as missing; a malformed UTF-8 byte does. Railway lab, on top of 0a5dad1f0: 17/17 tests pass, 0 vacuous. Mutants: 9 of 10 killed. The survivor, MAX_S = 61, is equivalent, since reels grow in 72-frame steps and 60..62 s refuse the same ideas. Generated binaries: A{B} and a 25-scene idea write 0 bytes; the same A{B} with the guard removed writes 144 whole frames (the control); the sample idea writes 216 whole frames. t27b front end: 0 unsupported constructs. Seal re-saved, all hashes MATCH. The t27f skill records the check and the equivalent-mutant note. Co-Authored-By: Claude Opus 5.5 --- .claude/skills/t27f/SKILL.md | 10 +++ .trinity/seals/t27f_idea_factory.json | 16 ++--- specs/t27f/idea_factory.t27 | 97 +++++++++++++++++++++++++++ 3 files changed, 115 insertions(+), 8 deletions(-) diff --git a/.claude/skills/t27f/SKILL.md b/.claude/skills/t27f/SKILL.md index 9d9e9d44f3..3d67bb002f 100644 --- a/.claude/skills/t27f/SKILL.md +++ b/.claude/skills/t27f/SKILL.md @@ -427,6 +427,16 @@ of particles, and runnable in the OS itself and on FPGA. Two specs do this: Five parallel libx264 encoders on the lab failed with "Error while opening encoder", the pipe closed, and md5 hashed a truncated stream: 3 of 5 read DIFF. Run md5 separately, then encode one at a time. A DIFF that coincides with an encoder error is the harness, not the code. +- **Run `preflight(IDEA)` before you render an idea** (gHashTag/t27#10820). It returns `PF_EMPTY` + (no scene), `PF_GLYPH` (a character glyph57 would draw as `?`, such as phi, a euro sign, `{`, `~` + or a malformed byte; `first_missing` gives its byte offset), `PF_LONG` (the reel would run past + `MAX_S` = 60 s) or `PF_OK`. `main` writes **0 bytes** for a refused idea, so an empty output file + means the idea was refused, not that the render broke. Rewrite the idea instead: `PHI^2`, not the + Greek letter. **Check the guard on the binary, with a control:** the same refused idea built with + the guard line `sed`-ed out must write frames (144 for `A{B}`), or the 0 bytes prove nothing. + **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. **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 c0bf1f04d2..18b4f077be 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:bb1ef6081add1e9fa249288e6ebd223bc66be7f13d91210eed6dac48ed4a01aa", - "gen_hash_rust": "sha256:2420620e8b22b844cf0912ec4a21d7358a0045cec3508d8a14c6d8206f169ccb", - "gen_hash_verilog": "sha256:8f8cd1625c7f3d516bc5e597c4860b81f2a4860ce6d6f3cb4b06c2578568a49d", - "gen_hash_zig": "sha256:b4136a42488a6715a58f8a2e2e482871209c94a0009a78cab7a1f357db42c0d2", + "gen_hash_c": "sha256:b4cb42ec55deec10b3180769d04e36217f39dc786c7756387880cf26bc7f3f15", + "gen_hash_rust": "sha256:7632796cc4ee12e7d310e8115091e72248074a2e25ff02e8773ea5776ec6b2d1", + "gen_hash_verilog": "sha256:a1e8068e1df97e43a750658c626f76e47f52b7d62f249bc598af057a78a9e35f", + "gen_hash_zig": "sha256:6b2f7675bb3198c6045686c29eac989768cb5d3e0b00b14795be854be56a8c9f", "module": "idea_factory", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-19T03:28:21Z", + "sealed_at": "2026-10-19T03:41:58Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:a68fbc93ea94b32e264ae91b82b149eb7530c3af4e2787c8b457a2eeec0c3482", + "spec_hash": "sha256:44432d34553ebeb531b415544066d45f5928975c5a98fb95aa7bd69267430a5a", "spec_path": "specs/t27f/idea_factory.t27", "tests": { "failed": 0, "failing": [], "forced": false, - "passed": 14, - "total": 14, + "passed": 17, + "total": 17, "vacuous": 0 }, "toolchain": { diff --git a/specs/t27f/idea_factory.t27 b/specs/t27f/idea_factory.t27 index 9ce8b39d95..e7c0429f92 100644 --- a/specs/t27f/idea_factory.t27 +++ b/specs/t27f/idea_factory.t27 @@ -26,6 +26,9 @@ // A RUSSIAN IDEA is a u8 array of UTF-8 bytes (L3 keeps sources ASCII, and t27c keeps "\xNN" as // four characters): RU_IDEA below, passed as RU_IDEA[0..80]. // +// PRE-FLIGHT: main draws nothing for an idea preflight refuses -- empty, a character the font +// lacks, or a reel longer than MAX_S. +// // 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 @@ -33,6 +36,8 @@ module idea_factory; use t27f::glyph57::glyph57_on; +use t27f::glyph57::glyph57_of; +use t27f::glyph57::QMARK; use t27f::glyph57::cp_len; use t27f::glyph57::cp_at; use t27f::glyph57::cp_count; @@ -304,6 +309,48 @@ pub fn pick_preset(t: []const u8, p: usize) -> usize { return p; } +// ---- pre-flight: an idea the factory cannot draw is refused before a frame (gHashTag/t27#10820) ---- + +// glyph57 draws a character it does not have as '?', so without this check an idea with a phi or +// a euro sign ships with the wrong letter on screen, and nothing notices. +pub const PF_OK : i32 = 0; +pub const PF_EMPTY : i32 = 1; // no scene: nothing but spaces and cuts +pub const PF_GLYPH : i32 = 2; // a character the font draws as '?' +pub const PF_LONG : i32 = 3; // the reel would run past MAX_S +pub const MAX_S : i32 = 60; // the factory's own cap on a reel, seconds + +// The characters of t that the font draws as '?': what 0 = how many, 1 = the byte offset of the +// first (-1 when there is none). A real '?', a '|' cut and a malformed byte are decided by code +// point: '?' and '|' do not count, BAD does. +pub fn missing_walk(t: []const u8, what: i32) -> i32 { + const n = @as(i32, @intCast(t.len)); + var i : i32 = 0; + var m : i32 = 0; + while (i < n) { + const c = cp_at(t, i); + if (c != 63 && c != 124 && glyph57_of(c) == QMARK) { + if (what == 1) { return i; } + m = m + 1; + } + i = i + cp_len(t, i); + } + if (what == 1) { return 0 - 1; } + return m; +} +pub fn missing_glyphs(t: []const u8) -> i32 { + return missing_walk(t, 0); +} +pub fn first_missing(t: []const u8) -> i32 { + return missing_walk(t, 1); +} +// Why the factory would refuse idea t, checked in this order; PF_OK when it would draw it. +pub fn preflight(t: []const u8) -> i32 { + if (scene_count(t) == 0) { return PF_EMPTY; } + if (missing_glyphs(t) > 0) { return PF_GLYPH; } + if (reel_frames(t) > MAX_S * FPS) { return PF_LONG; } + return PF_OK; +} + // ---- assign ---- // Where card particle m of mm sits on the border of the rectangle (x0, y0)-(x1, y1): what 0 = x, @@ -361,6 +408,7 @@ pub fn main() -> void { var accb : [1080]i32 = undefined; var row : [3240]u8 = undefined; + if (preflight(IDEA) != PF_OK) { return; } // refused: not one frame, so no reel ships wrong const p = pick_preset(IDEA, PRESET); const rr = reach(p); var d2 : i32 = 0; @@ -721,3 +769,52 @@ test "a batch of five ideas covers all five presets under PRESET_AUTO" { assert(preset_of("THE FPGA RUNS|THE SPEC") == P_PAPER); assert(preset_of("SEALED|EVERY HASH") == P_RAIN); } + +// ---- pre-flight (gHashTag/t27#10820) ---- +// UTF-8 bytes, since L3 keeps the source ASCII: phi (CF 86) " = 1"; "A " and the euro sign +// (E2 82 AC); "A", a stray continuation byte, "B". +pub const PF_PHI : [6]u8 = [207, 134, 32, 61, 32, 49]; +pub const PF_EURO : [5]u8 = [65, 32, 226, 130, 172]; +pub const PF_STRAY : [3]u8 = [65, 150, 66]; + +test "pre-flight counts the characters the font would draw as a question mark" { + assert(missing_glyphs("PARTICLES BUILD EVERY FRAME") == 0); + assert(missing_glyphs("WHY?") == 0); // a real '?' is drawn as itself + assert(missing_glyphs("A|B") == 0); // a cut is not drawn + assert(missing_glyphs("lower case folds") == 0); + assert(missing_glyphs(RU_IDEA[0..80]) == 0); + assert(missing_glyphs("A{B}") == 2); + assert(missing_glyphs("X~Y") == 1); + assert(missing_glyphs(PF_PHI[0..6]) == 1); + assert(missing_glyphs(PF_EURO[0..5]) == 1); // one code point, three bytes + assert(missing_glyphs(PF_STRAY[0..3]) == 1); // a malformed byte reads as BAD + assert(first_missing("WHY?") == 0 - 1); + assert(first_missing("A{B}") == 1); + assert(first_missing(PF_PHI[0..6]) == 0); + assert(first_missing(PF_EURO[0..5]) == 2); + assert(first_missing(PF_STRAY[0..3]) == 1); +} + +test "pre-flight refuses an empty idea, a missing glyph and a reel past MAX_S, in that order" { + assert(preflight("") == PF_EMPTY); + assert(preflight(" | | ") == PF_EMPTY); + assert(preflight(PF_PHI[0..6]) == PF_GLYPH); + assert(preflight(PF_EURO[0..5]) == PF_GLYPH); + assert(preflight(PF_STRAY[0..3]) == PF_GLYPH); + // 24 scenes and the address: 25 * 72 = 1800 frames, exactly MAX_S * FPS. + assert(reel_frames("A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|T|U|V|W|X") == 1800); + assert(preflight("A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|T|U|V|W|X") == PF_OK); + assert(preflight("A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|T|U|V|W|X|Y") == PF_LONG); + assert(preflight("{|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|T|U|V|W|X|Y") == PF_GLYPH); +} + +test "every idea this file renders passes pre-flight" { + assert(preflight(IDEA) == PF_OK); + assert(preflight(CTA) == PF_OK); + assert(preflight(RU_IDEA[0..80]) == PF_OK); + assert(preflight("SPEC IS THE SOURCE|EVERY VIEW DERIVED") == PF_OK); + assert(preflight("ONE LAW|MANY SURFACES") == PF_OK); + assert(preflight("THREE STATES|BEAT TWO") == PF_OK); + assert(preflight("THE FPGA RUNS|THE SPEC") == PF_OK); + assert(preflight("SEALED|EVERY HASH") == PF_OK); +} From fe4fd88141e0ec14294a52602144683a943ae210 Mon Sep 17 00:00:00 2001 From: Dmitrii Vasilev Date: Mon, 19 Oct 2026 10:55:01 +0700 Subject: [PATCH 11/21] Ledger: sar_switch sorts after sar_comparator (Refs #10788) 4f739c161 put the sar_switch row before sar_comparator, so the loop-tools gate failed 'ledger: sorted, one entry per spec'. This commit also carries the merge of master, which brings move_gate's row and the cap of 139. Co-Authored-By: Claude Opus 5.5 --- docs/reports/t27b_expectations.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/reports/t27b_expectations.json b/docs/reports/t27b_expectations.json index 36dcea4106..49ed03b99b 100644 --- a/docs/reports/t27b_expectations.json +++ b/docs/reports/t27b_expectations.json @@ -50,8 +50,8 @@ {"path": "specs/analog/mos_psp103_data.t27", "t27b": "pass"}, {"path": "specs/analog/mos_table.t27", "t27b": "pass"}, {"path": "specs/analog/sar_cdac.t27", "t27b": "pass"}, - {"path": "specs/analog/sar_switch.t27", "t27b": "pass"}, {"path": "specs/analog/sar_comparator.t27", "t27b": "pass"}, + {"path": "specs/analog/sar_switch.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"}, From 5929aa051a4ef87489746f21f92fbd709d78cae5 Mon Sep 17 00:00:00 2001 From: Dmitrii Vasilev Date: Mon, 19 Oct 2026 11:00:27 +0700 Subject: [PATCH 12/21] t27f: idea factory cover still -- COVER=1 writes the reel's settled title frame, byte for byte (Closes #10828) Reels, Shorts and TikTok show a cover before a reel plays. Until now someone had to cut it out with ffmpeg and guess the frame. COVER = 1 makes main run the physics of every frame up to cover_frame(IDEA), the first scene's last frame (71), and raster only that one. The still is then frame 71 of the reel, with the same idea and preset, and cannot drift from it. Remotion's renderStill is the model. Railway lab, on top of e9b55d645: 18/18 tests pass, 0 vacuous. 4 of 4 mutants are killed. The t27b front end reports 0 unsupported constructs. Generated binaries: - COVER = 0 is md5-identical to the binary before this change. - COVER = 1 writes 6220800 bytes, and cmp finds it equal to frame 71 cut from the full 216-frame reel. - The control, with the guard moved to frames - 2, differs from frame 71 and equals frame 70. - A refused idea (A{B}) still writes 0 bytes. - The AUTO-preset idea SEALED|EVERY HASH writes one frame. Seal re-saved; all hashes MATCH. The t27f skill records the cover recipe. It also records the ledger sort trap that turned #10792 red: a row was inserted out of order, and `sort -c` catches it before the commit. Co-Authored-By: Claude Opus 5.5 --- .claude/skills/t27f/SKILL.md | 15 +++++++++++++ .trinity/seals/t27f_idea_factory.json | 16 +++++++------- specs/t27f/idea_factory.t27 | 31 ++++++++++++++++++++++++++- 3 files changed, 53 insertions(+), 9 deletions(-) diff --git a/.claude/skills/t27f/SKILL.md b/.claude/skills/t27f/SKILL.md index 3d67bb002f..b52b0624b1 100644 --- a/.claude/skills/t27f/SKILL.md +++ b/.claude/skills/t27f/SKILL.md @@ -437,6 +437,21 @@ of particles, and runnable in the OS itself and on FPGA. Two specs do this: **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 18b4f077be..d96fd93cc3 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-19T03:41:58Z", + "sealed_at": "2026-10-19T03:58:45Z", "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); +} From ea41efb1c9d639a62f0ce4bcd651d850b2a64de5 Mon Sep 17 00:00:00 2001 From: Dmitrii Vasilev Date: Mon, 19 Oct 2026 12:46:00 +0700 Subject: [PATCH 13/21] t27b ledger: master's rows plus this lane's glyph57 row, so the branch merges clean (Refs #10609) Co-Authored-By: Claude Opus 5.5 --- docs/reports/t27b_expectations.json | 36 +++++++++++++++++------------ 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/docs/reports/t27b_expectations.json b/docs/reports/t27b_expectations.json index 49ed03b99b..f10eb0150f 100644 --- a/docs/reports/t27b_expectations.json +++ b/docs/reports/t27b_expectations.json @@ -12,7 +12,7 @@ "reference-bug": "the reference passes it for the wrong reason; t27b is right not to", "n/a": "outside what t27b is for (say why in the PR that sets it)" }, - "max_not_pass": 139, + "max_not_pass": 129, "entries": [ {"path": "specs/a/b_c.t27", "t27b": "pass"}, {"path": "specs/a_b/c.t27", "t27b": "pass"}, @@ -51,6 +51,7 @@ {"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/analog/sar_logic.t27", "t27b": "pass"}, {"path": "specs/analog/sar_switch.t27", "t27b": "pass"}, {"path": "specs/api/c_abi.t27", "t27b": "pass"}, {"path": "specs/api/tri_api_context.t27", "t27b": "pass"}, @@ -244,6 +245,7 @@ {"path": "specs/compiler/rust_optional_returns.t27", "t27b": "pass"}, {"path": "specs/compiler/rust_tail_returns.t27", "t27b": "pass"}, {"path": "specs/compiler/rust_value_parameters.t27", "t27b": "pass"}, + {"path": "specs/compiler/string_escapes.t27", "t27b": "pass"}, {"path": "specs/compiler/test_report_exit.t27", "t27b": "pass"}, {"path": "specs/compiler/theory/isa_round_trip.t27", "t27b": "pass"}, {"path": "specs/compiler/theory/reproducible.t27", "t27b": "pass"}, @@ -459,7 +461,7 @@ {"path": "specs/hosting/sybil.t27", "t27b": "pass"}, {"path": "specs/hosting/toolchain.t27", "t27b": "pass"}, {"path": "specs/hosting/topology.t27", "t27b": "pass"}, - {"path": "specs/hslm/forward_pass.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, + {"path": "specs/hslm/forward_pass.t27", "t27b": "pass"}, {"path": "specs/i18n/agents-ru.t27", "t27b": "pass"}, {"path": "specs/i18n/blog-ru.t27", "t27b": "pass"}, {"path": "specs/i18n/docs-ru.t27", "t27b": "pass"}, @@ -468,11 +470,12 @@ {"path": "specs/igla/coder/benchmark.t27", "t27b": "blocked", "blocker": "ExprIdentifier(undeclared)", "reason": "unimplemented"}, {"path": "specs/igla/coder/dataset.t27", "t27b": "blocked", "blocker": "ExprIdentifier(undeclared)", "reason": "unimplemented"}, {"path": "specs/igla/coder/eval.t27", "t27b": "blocked", "blocker": "ExprIdentifier(undeclared)", "reason": "unimplemented"}, - {"path": "specs/igla/coder/kill_rule.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, + {"path": "specs/igla/coder/kill_rule.t27", "t27b": "pass"}, {"path": "specs/igla/coder/pipeline.t27", "t27b": "blocked", "blocker": "ConstDecl(slice)", "reason": "unimplemented"}, {"path": "specs/igla/coder/preflight.t27", "t27b": "pass"}, {"path": "specs/igla/coder/prm.t27", "t27b": "pass"}, {"path": "specs/igla/coder/ternary_gemm_gpu.t27", "t27b": "pass"}, + {"path": "specs/igla/coder/ternary_gemm_ptx.t27", "t27b": "pass"}, {"path": "specs/igla/coder/tokenizer.t27", "t27b": "pass"}, {"path": "specs/igla/coder/training.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, {"path": "specs/igla/coder/weights.t27", "t27b": "blocked", "blocker": "ConstDecl(slice)", "reason": "unimplemented"}, @@ -547,32 +550,33 @@ {"path": "specs/ml/arena_heap.t27", "t27b": "pass"}, {"path": "specs/ml/file_io_contract.t27", "t27b": "pass"}, {"path": "specs/ml/host_io_model.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, + {"path": "specs/ml/hslm_corpus.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, {"path": "specs/ml/igla_champion_capsule.t27", "t27b": "blocked", "blocker": "ExprIdentifier(undeclared)", "reason": "unimplemented"}, {"path": "specs/ml/layers/avgpool2d_layer.t27", "t27b": "pass"}, - {"path": "specs/ml/layers/batchnorm_layer.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, + {"path": "specs/ml/layers/batchnorm_layer.t27", "t27b": "pass"}, {"path": "specs/ml/layers/conv2d_layer.t27", "t27b": "pass"}, {"path": "specs/ml/layers/dense_layer.t27", "t27b": "pass"}, {"path": "specs/ml/layers/dropout_layer.t27", "t27b": "pass"}, - {"path": "specs/ml/layers/embedding_layer.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, + {"path": "specs/ml/layers/embedding_layer.t27", "t27b": "pass"}, {"path": "specs/ml/layers/flatten_layer.t27", "t27b": "pass"}, {"path": "specs/ml/layers/layernorm_layer.t27", "t27b": "pass"}, {"path": "specs/ml/layers/maxpool2d_layer.t27", "t27b": "pass"}, {"path": "specs/ml/layers/residual_connection.t27", "t27b": "pass"}, {"path": "specs/ml/loss/binary_crossentropy_loss.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, - {"path": "specs/ml/loss/contrastive_loss.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, + {"path": "specs/ml/loss/contrastive_loss.t27", "t27b": "pass"}, {"path": "specs/ml/loss/cross_entropy_loss.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, {"path": "specs/ml/loss/huber_loss.t27", "t27b": "pass"}, {"path": "specs/ml/loss/kl_divergence.t27", "t27b": "pass"}, {"path": "specs/ml/loss/mse_loss.t27", "t27b": "pass"}, - {"path": "specs/ml/optimizer/adagrad.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, - {"path": "specs/ml/optimizer/adam.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, - {"path": "specs/ml/optimizer/adamw.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, - {"path": "specs/ml/optimizer/lamb.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, + {"path": "specs/ml/optimizer/adagrad.t27", "t27b": "pass"}, + {"path": "specs/ml/optimizer/adam.t27", "t27b": "pass"}, + {"path": "specs/ml/optimizer/adamw.t27", "t27b": "pass"}, + {"path": "specs/ml/optimizer/lamb.t27", "t27b": "pass"}, {"path": "specs/ml/optimizer/race_config.t27", "t27b": "blocked", "blocker": "ExprIdentifier(undeclared)", "reason": "unimplemented"}, {"path": "specs/ml/optimizer/rmsprop.t27", "t27b": "pass"}, {"path": "specs/ml/optimizer/sgd.t27", "t27b": "pass"}, {"path": "specs/ml/optimizer/sgd_momentum.t27", "t27b": "blocked", "blocker": "ExprIdentifier(undeclared)", "reason": "unimplemented"}, - {"path": "specs/ml/pathway/mlp.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, + {"path": "specs/ml/pathway/mlp.t27", "t27b": "blocked", "blocker": "ExprArrayLiteral(repeat count)", "reason": "unimplemented"}, {"path": "specs/ml/recurrent/attention_mechanism.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, {"path": "specs/ml/recurrent/bilstm.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, {"path": "specs/ml/recurrent/gru_cell.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, @@ -581,7 +585,7 @@ {"path": "specs/ml/recurrent/rnn_cell.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, {"path": "specs/ml/recurrent/self_attention.t27", "t27b": "pass"}, {"path": "specs/ml/recurrent/seq2seq.t27", "t27b": "pass"}, - {"path": "specs/ml/rl/advantage_estimator.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, + {"path": "specs/ml/rl/advantage_estimator.t27", "t27b": "pass"}, {"path": "specs/ml/rl/dqn.t27", "t27b": "pass"}, {"path": "specs/ml/rl/dqn_target_network.t27", "t27b": "pass"}, {"path": "specs/ml/rl/ppo_actor.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, @@ -609,11 +613,12 @@ {"path": "specs/network/quorum.t27", "t27b": "pass"}, {"path": "specs/network/usage.t27", "t27b": "pass"}, {"path": "specs/nn/attention.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, - {"path": "specs/nn/gla.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, + {"path": "specs/nn/gla.t27", "t27b": "blocked", "blocker": "ConstDecl", "reason": "unimplemented"}, {"path": "specs/nn/hslm.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, {"path": "specs/nn/hslm_backward.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, {"path": "specs/nn/hslm_data.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, - {"path": "specs/nn/ternary_ste.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, + {"path": "specs/nn/hslm_tokenizer.t27", "t27b": "pass"}, + {"path": "specs/nn/ternary_ste.t27", "t27b": "pass"}, {"path": "specs/numeric/bnf1024.t27", "t27b": "pass"}, {"path": "specs/numeric/bnf128.t27", "t27b": "pass"}, {"path": "specs/numeric/bnf16.t27", "t27b": "pass"}, @@ -686,6 +691,7 @@ {"path": "specs/policy/own_language.t27", "t27b": "pass"}, {"path": "specs/policy/pr_budget.t27", "t27b": "pass"}, {"path": "specs/policy/required_contexts.t27", "t27b": "pass"}, + {"path": "specs/policy/secret_gate.t27", "t27b": "pass"}, {"path": "specs/policy/text_lines.t27", "t27b": "pass"}, {"path": "specs/port/bootstrap/src/bitnet_irq.t27", "t27b": "pass"}, {"path": "specs/port/bootstrap/src/bridge.t27", "t27b": "pass"}, @@ -1987,7 +1993,7 @@ {"path": "specs/tri/utils/terminal.t27", "t27b": "blocked", "blocker": "ExprCall(@import)", "reason": "unimplemented"}, {"path": "specs/tri/utils/text.t27", "t27b": "pass"}, {"path": "specs/tri/utils/time.t27", "t27b": "blocked", "blocker": "ExprCall(@divFloor)", "reason": "unimplemented"}, - {"path": "specs/tri/utils/utf8.t27", "t27b": "blocked", "blocker": "ExprCall(@truncate)", "reason": "unimplemented"}, + {"path": "specs/tri/utils/utf8.t27", "t27b": "pass"}, {"path": "specs/tri/utils/version.t27", "t27b": "pass"}, {"path": "specs/trinet/eth_top_ax7203.t27", "t27b": "pass"}, {"path": "specs/trinet/etx.t27", "t27b": "pass"}, From b07f7bf3634c0fdc864542e7cc6dfc86d11b53c4 Mon Sep 17 00:00:00 2001 From: Dmitrii Vasilev Date: Mon, 19 Oct 2026 12:46:04 +0700 Subject: [PATCH 14/21] t27b ledger: master's rows plus this lane's glyph57 row, so the branch merges clean (Refs #10609) Co-Authored-By: Claude Opus 5.5 --- docs/reports/t27b_expectations.json | 36 +++++++++++++++++------------ 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/docs/reports/t27b_expectations.json b/docs/reports/t27b_expectations.json index 49ed03b99b..f10eb0150f 100644 --- a/docs/reports/t27b_expectations.json +++ b/docs/reports/t27b_expectations.json @@ -12,7 +12,7 @@ "reference-bug": "the reference passes it for the wrong reason; t27b is right not to", "n/a": "outside what t27b is for (say why in the PR that sets it)" }, - "max_not_pass": 139, + "max_not_pass": 129, "entries": [ {"path": "specs/a/b_c.t27", "t27b": "pass"}, {"path": "specs/a_b/c.t27", "t27b": "pass"}, @@ -51,6 +51,7 @@ {"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/analog/sar_logic.t27", "t27b": "pass"}, {"path": "specs/analog/sar_switch.t27", "t27b": "pass"}, {"path": "specs/api/c_abi.t27", "t27b": "pass"}, {"path": "specs/api/tri_api_context.t27", "t27b": "pass"}, @@ -244,6 +245,7 @@ {"path": "specs/compiler/rust_optional_returns.t27", "t27b": "pass"}, {"path": "specs/compiler/rust_tail_returns.t27", "t27b": "pass"}, {"path": "specs/compiler/rust_value_parameters.t27", "t27b": "pass"}, + {"path": "specs/compiler/string_escapes.t27", "t27b": "pass"}, {"path": "specs/compiler/test_report_exit.t27", "t27b": "pass"}, {"path": "specs/compiler/theory/isa_round_trip.t27", "t27b": "pass"}, {"path": "specs/compiler/theory/reproducible.t27", "t27b": "pass"}, @@ -459,7 +461,7 @@ {"path": "specs/hosting/sybil.t27", "t27b": "pass"}, {"path": "specs/hosting/toolchain.t27", "t27b": "pass"}, {"path": "specs/hosting/topology.t27", "t27b": "pass"}, - {"path": "specs/hslm/forward_pass.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, + {"path": "specs/hslm/forward_pass.t27", "t27b": "pass"}, {"path": "specs/i18n/agents-ru.t27", "t27b": "pass"}, {"path": "specs/i18n/blog-ru.t27", "t27b": "pass"}, {"path": "specs/i18n/docs-ru.t27", "t27b": "pass"}, @@ -468,11 +470,12 @@ {"path": "specs/igla/coder/benchmark.t27", "t27b": "blocked", "blocker": "ExprIdentifier(undeclared)", "reason": "unimplemented"}, {"path": "specs/igla/coder/dataset.t27", "t27b": "blocked", "blocker": "ExprIdentifier(undeclared)", "reason": "unimplemented"}, {"path": "specs/igla/coder/eval.t27", "t27b": "blocked", "blocker": "ExprIdentifier(undeclared)", "reason": "unimplemented"}, - {"path": "specs/igla/coder/kill_rule.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, + {"path": "specs/igla/coder/kill_rule.t27", "t27b": "pass"}, {"path": "specs/igla/coder/pipeline.t27", "t27b": "blocked", "blocker": "ConstDecl(slice)", "reason": "unimplemented"}, {"path": "specs/igla/coder/preflight.t27", "t27b": "pass"}, {"path": "specs/igla/coder/prm.t27", "t27b": "pass"}, {"path": "specs/igla/coder/ternary_gemm_gpu.t27", "t27b": "pass"}, + {"path": "specs/igla/coder/ternary_gemm_ptx.t27", "t27b": "pass"}, {"path": "specs/igla/coder/tokenizer.t27", "t27b": "pass"}, {"path": "specs/igla/coder/training.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, {"path": "specs/igla/coder/weights.t27", "t27b": "blocked", "blocker": "ConstDecl(slice)", "reason": "unimplemented"}, @@ -547,32 +550,33 @@ {"path": "specs/ml/arena_heap.t27", "t27b": "pass"}, {"path": "specs/ml/file_io_contract.t27", "t27b": "pass"}, {"path": "specs/ml/host_io_model.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, + {"path": "specs/ml/hslm_corpus.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, {"path": "specs/ml/igla_champion_capsule.t27", "t27b": "blocked", "blocker": "ExprIdentifier(undeclared)", "reason": "unimplemented"}, {"path": "specs/ml/layers/avgpool2d_layer.t27", "t27b": "pass"}, - {"path": "specs/ml/layers/batchnorm_layer.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, + {"path": "specs/ml/layers/batchnorm_layer.t27", "t27b": "pass"}, {"path": "specs/ml/layers/conv2d_layer.t27", "t27b": "pass"}, {"path": "specs/ml/layers/dense_layer.t27", "t27b": "pass"}, {"path": "specs/ml/layers/dropout_layer.t27", "t27b": "pass"}, - {"path": "specs/ml/layers/embedding_layer.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, + {"path": "specs/ml/layers/embedding_layer.t27", "t27b": "pass"}, {"path": "specs/ml/layers/flatten_layer.t27", "t27b": "pass"}, {"path": "specs/ml/layers/layernorm_layer.t27", "t27b": "pass"}, {"path": "specs/ml/layers/maxpool2d_layer.t27", "t27b": "pass"}, {"path": "specs/ml/layers/residual_connection.t27", "t27b": "pass"}, {"path": "specs/ml/loss/binary_crossentropy_loss.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, - {"path": "specs/ml/loss/contrastive_loss.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, + {"path": "specs/ml/loss/contrastive_loss.t27", "t27b": "pass"}, {"path": "specs/ml/loss/cross_entropy_loss.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, {"path": "specs/ml/loss/huber_loss.t27", "t27b": "pass"}, {"path": "specs/ml/loss/kl_divergence.t27", "t27b": "pass"}, {"path": "specs/ml/loss/mse_loss.t27", "t27b": "pass"}, - {"path": "specs/ml/optimizer/adagrad.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, - {"path": "specs/ml/optimizer/adam.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, - {"path": "specs/ml/optimizer/adamw.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, - {"path": "specs/ml/optimizer/lamb.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, + {"path": "specs/ml/optimizer/adagrad.t27", "t27b": "pass"}, + {"path": "specs/ml/optimizer/adam.t27", "t27b": "pass"}, + {"path": "specs/ml/optimizer/adamw.t27", "t27b": "pass"}, + {"path": "specs/ml/optimizer/lamb.t27", "t27b": "pass"}, {"path": "specs/ml/optimizer/race_config.t27", "t27b": "blocked", "blocker": "ExprIdentifier(undeclared)", "reason": "unimplemented"}, {"path": "specs/ml/optimizer/rmsprop.t27", "t27b": "pass"}, {"path": "specs/ml/optimizer/sgd.t27", "t27b": "pass"}, {"path": "specs/ml/optimizer/sgd_momentum.t27", "t27b": "blocked", "blocker": "ExprIdentifier(undeclared)", "reason": "unimplemented"}, - {"path": "specs/ml/pathway/mlp.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, + {"path": "specs/ml/pathway/mlp.t27", "t27b": "blocked", "blocker": "ExprArrayLiteral(repeat count)", "reason": "unimplemented"}, {"path": "specs/ml/recurrent/attention_mechanism.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, {"path": "specs/ml/recurrent/bilstm.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, {"path": "specs/ml/recurrent/gru_cell.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, @@ -581,7 +585,7 @@ {"path": "specs/ml/recurrent/rnn_cell.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, {"path": "specs/ml/recurrent/self_attention.t27", "t27b": "pass"}, {"path": "specs/ml/recurrent/seq2seq.t27", "t27b": "pass"}, - {"path": "specs/ml/rl/advantage_estimator.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, + {"path": "specs/ml/rl/advantage_estimator.t27", "t27b": "pass"}, {"path": "specs/ml/rl/dqn.t27", "t27b": "pass"}, {"path": "specs/ml/rl/dqn_target_network.t27", "t27b": "pass"}, {"path": "specs/ml/rl/ppo_actor.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, @@ -609,11 +613,12 @@ {"path": "specs/network/quorum.t27", "t27b": "pass"}, {"path": "specs/network/usage.t27", "t27b": "pass"}, {"path": "specs/nn/attention.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, - {"path": "specs/nn/gla.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, + {"path": "specs/nn/gla.t27", "t27b": "blocked", "blocker": "ConstDecl", "reason": "unimplemented"}, {"path": "specs/nn/hslm.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, {"path": "specs/nn/hslm_backward.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, {"path": "specs/nn/hslm_data.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, - {"path": "specs/nn/ternary_ste.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, + {"path": "specs/nn/hslm_tokenizer.t27", "t27b": "pass"}, + {"path": "specs/nn/ternary_ste.t27", "t27b": "pass"}, {"path": "specs/numeric/bnf1024.t27", "t27b": "pass"}, {"path": "specs/numeric/bnf128.t27", "t27b": "pass"}, {"path": "specs/numeric/bnf16.t27", "t27b": "pass"}, @@ -686,6 +691,7 @@ {"path": "specs/policy/own_language.t27", "t27b": "pass"}, {"path": "specs/policy/pr_budget.t27", "t27b": "pass"}, {"path": "specs/policy/required_contexts.t27", "t27b": "pass"}, + {"path": "specs/policy/secret_gate.t27", "t27b": "pass"}, {"path": "specs/policy/text_lines.t27", "t27b": "pass"}, {"path": "specs/port/bootstrap/src/bitnet_irq.t27", "t27b": "pass"}, {"path": "specs/port/bootstrap/src/bridge.t27", "t27b": "pass"}, @@ -1987,7 +1993,7 @@ {"path": "specs/tri/utils/terminal.t27", "t27b": "blocked", "blocker": "ExprCall(@import)", "reason": "unimplemented"}, {"path": "specs/tri/utils/text.t27", "t27b": "pass"}, {"path": "specs/tri/utils/time.t27", "t27b": "blocked", "blocker": "ExprCall(@divFloor)", "reason": "unimplemented"}, - {"path": "specs/tri/utils/utf8.t27", "t27b": "blocked", "blocker": "ExprCall(@truncate)", "reason": "unimplemented"}, + {"path": "specs/tri/utils/utf8.t27", "t27b": "pass"}, {"path": "specs/tri/utils/version.t27", "t27b": "pass"}, {"path": "specs/trinet/eth_top_ax7203.t27", "t27b": "pass"}, {"path": "specs/trinet/etx.t27", "t27b": "pass"}, From c158873dedca62fd2da42a5684e8509063999dc8 Mon Sep 17 00:00:00 2001 From: Dmitrii Vasilev Date: Mon, 19 Oct 2026 12:46:17 +0700 Subject: [PATCH 15/21] t27b ledger: master's rows plus this lane's own rows, so the branch merges clean (Refs #10609) Co-Authored-By: Claude Opus 5.5 --- docs/reports/t27b_expectations.json | 36 +++++++++++++++++------------ 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/docs/reports/t27b_expectations.json b/docs/reports/t27b_expectations.json index 49ed03b99b..f10eb0150f 100644 --- a/docs/reports/t27b_expectations.json +++ b/docs/reports/t27b_expectations.json @@ -12,7 +12,7 @@ "reference-bug": "the reference passes it for the wrong reason; t27b is right not to", "n/a": "outside what t27b is for (say why in the PR that sets it)" }, - "max_not_pass": 139, + "max_not_pass": 129, "entries": [ {"path": "specs/a/b_c.t27", "t27b": "pass"}, {"path": "specs/a_b/c.t27", "t27b": "pass"}, @@ -51,6 +51,7 @@ {"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/analog/sar_logic.t27", "t27b": "pass"}, {"path": "specs/analog/sar_switch.t27", "t27b": "pass"}, {"path": "specs/api/c_abi.t27", "t27b": "pass"}, {"path": "specs/api/tri_api_context.t27", "t27b": "pass"}, @@ -244,6 +245,7 @@ {"path": "specs/compiler/rust_optional_returns.t27", "t27b": "pass"}, {"path": "specs/compiler/rust_tail_returns.t27", "t27b": "pass"}, {"path": "specs/compiler/rust_value_parameters.t27", "t27b": "pass"}, + {"path": "specs/compiler/string_escapes.t27", "t27b": "pass"}, {"path": "specs/compiler/test_report_exit.t27", "t27b": "pass"}, {"path": "specs/compiler/theory/isa_round_trip.t27", "t27b": "pass"}, {"path": "specs/compiler/theory/reproducible.t27", "t27b": "pass"}, @@ -459,7 +461,7 @@ {"path": "specs/hosting/sybil.t27", "t27b": "pass"}, {"path": "specs/hosting/toolchain.t27", "t27b": "pass"}, {"path": "specs/hosting/topology.t27", "t27b": "pass"}, - {"path": "specs/hslm/forward_pass.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, + {"path": "specs/hslm/forward_pass.t27", "t27b": "pass"}, {"path": "specs/i18n/agents-ru.t27", "t27b": "pass"}, {"path": "specs/i18n/blog-ru.t27", "t27b": "pass"}, {"path": "specs/i18n/docs-ru.t27", "t27b": "pass"}, @@ -468,11 +470,12 @@ {"path": "specs/igla/coder/benchmark.t27", "t27b": "blocked", "blocker": "ExprIdentifier(undeclared)", "reason": "unimplemented"}, {"path": "specs/igla/coder/dataset.t27", "t27b": "blocked", "blocker": "ExprIdentifier(undeclared)", "reason": "unimplemented"}, {"path": "specs/igla/coder/eval.t27", "t27b": "blocked", "blocker": "ExprIdentifier(undeclared)", "reason": "unimplemented"}, - {"path": "specs/igla/coder/kill_rule.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, + {"path": "specs/igla/coder/kill_rule.t27", "t27b": "pass"}, {"path": "specs/igla/coder/pipeline.t27", "t27b": "blocked", "blocker": "ConstDecl(slice)", "reason": "unimplemented"}, {"path": "specs/igla/coder/preflight.t27", "t27b": "pass"}, {"path": "specs/igla/coder/prm.t27", "t27b": "pass"}, {"path": "specs/igla/coder/ternary_gemm_gpu.t27", "t27b": "pass"}, + {"path": "specs/igla/coder/ternary_gemm_ptx.t27", "t27b": "pass"}, {"path": "specs/igla/coder/tokenizer.t27", "t27b": "pass"}, {"path": "specs/igla/coder/training.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, {"path": "specs/igla/coder/weights.t27", "t27b": "blocked", "blocker": "ConstDecl(slice)", "reason": "unimplemented"}, @@ -547,32 +550,33 @@ {"path": "specs/ml/arena_heap.t27", "t27b": "pass"}, {"path": "specs/ml/file_io_contract.t27", "t27b": "pass"}, {"path": "specs/ml/host_io_model.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, + {"path": "specs/ml/hslm_corpus.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, {"path": "specs/ml/igla_champion_capsule.t27", "t27b": "blocked", "blocker": "ExprIdentifier(undeclared)", "reason": "unimplemented"}, {"path": "specs/ml/layers/avgpool2d_layer.t27", "t27b": "pass"}, - {"path": "specs/ml/layers/batchnorm_layer.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, + {"path": "specs/ml/layers/batchnorm_layer.t27", "t27b": "pass"}, {"path": "specs/ml/layers/conv2d_layer.t27", "t27b": "pass"}, {"path": "specs/ml/layers/dense_layer.t27", "t27b": "pass"}, {"path": "specs/ml/layers/dropout_layer.t27", "t27b": "pass"}, - {"path": "specs/ml/layers/embedding_layer.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, + {"path": "specs/ml/layers/embedding_layer.t27", "t27b": "pass"}, {"path": "specs/ml/layers/flatten_layer.t27", "t27b": "pass"}, {"path": "specs/ml/layers/layernorm_layer.t27", "t27b": "pass"}, {"path": "specs/ml/layers/maxpool2d_layer.t27", "t27b": "pass"}, {"path": "specs/ml/layers/residual_connection.t27", "t27b": "pass"}, {"path": "specs/ml/loss/binary_crossentropy_loss.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, - {"path": "specs/ml/loss/contrastive_loss.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, + {"path": "specs/ml/loss/contrastive_loss.t27", "t27b": "pass"}, {"path": "specs/ml/loss/cross_entropy_loss.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, {"path": "specs/ml/loss/huber_loss.t27", "t27b": "pass"}, {"path": "specs/ml/loss/kl_divergence.t27", "t27b": "pass"}, {"path": "specs/ml/loss/mse_loss.t27", "t27b": "pass"}, - {"path": "specs/ml/optimizer/adagrad.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, - {"path": "specs/ml/optimizer/adam.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, - {"path": "specs/ml/optimizer/adamw.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, - {"path": "specs/ml/optimizer/lamb.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, + {"path": "specs/ml/optimizer/adagrad.t27", "t27b": "pass"}, + {"path": "specs/ml/optimizer/adam.t27", "t27b": "pass"}, + {"path": "specs/ml/optimizer/adamw.t27", "t27b": "pass"}, + {"path": "specs/ml/optimizer/lamb.t27", "t27b": "pass"}, {"path": "specs/ml/optimizer/race_config.t27", "t27b": "blocked", "blocker": "ExprIdentifier(undeclared)", "reason": "unimplemented"}, {"path": "specs/ml/optimizer/rmsprop.t27", "t27b": "pass"}, {"path": "specs/ml/optimizer/sgd.t27", "t27b": "pass"}, {"path": "specs/ml/optimizer/sgd_momentum.t27", "t27b": "blocked", "blocker": "ExprIdentifier(undeclared)", "reason": "unimplemented"}, - {"path": "specs/ml/pathway/mlp.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, + {"path": "specs/ml/pathway/mlp.t27", "t27b": "blocked", "blocker": "ExprArrayLiteral(repeat count)", "reason": "unimplemented"}, {"path": "specs/ml/recurrent/attention_mechanism.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, {"path": "specs/ml/recurrent/bilstm.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, {"path": "specs/ml/recurrent/gru_cell.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, @@ -581,7 +585,7 @@ {"path": "specs/ml/recurrent/rnn_cell.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, {"path": "specs/ml/recurrent/self_attention.t27", "t27b": "pass"}, {"path": "specs/ml/recurrent/seq2seq.t27", "t27b": "pass"}, - {"path": "specs/ml/rl/advantage_estimator.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, + {"path": "specs/ml/rl/advantage_estimator.t27", "t27b": "pass"}, {"path": "specs/ml/rl/dqn.t27", "t27b": "pass"}, {"path": "specs/ml/rl/dqn_target_network.t27", "t27b": "pass"}, {"path": "specs/ml/rl/ppo_actor.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, @@ -609,11 +613,12 @@ {"path": "specs/network/quorum.t27", "t27b": "pass"}, {"path": "specs/network/usage.t27", "t27b": "pass"}, {"path": "specs/nn/attention.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, - {"path": "specs/nn/gla.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, + {"path": "specs/nn/gla.t27", "t27b": "blocked", "blocker": "ConstDecl", "reason": "unimplemented"}, {"path": "specs/nn/hslm.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, {"path": "specs/nn/hslm_backward.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, {"path": "specs/nn/hslm_data.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, - {"path": "specs/nn/ternary_ste.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, + {"path": "specs/nn/hslm_tokenizer.t27", "t27b": "pass"}, + {"path": "specs/nn/ternary_ste.t27", "t27b": "pass"}, {"path": "specs/numeric/bnf1024.t27", "t27b": "pass"}, {"path": "specs/numeric/bnf128.t27", "t27b": "pass"}, {"path": "specs/numeric/bnf16.t27", "t27b": "pass"}, @@ -686,6 +691,7 @@ {"path": "specs/policy/own_language.t27", "t27b": "pass"}, {"path": "specs/policy/pr_budget.t27", "t27b": "pass"}, {"path": "specs/policy/required_contexts.t27", "t27b": "pass"}, + {"path": "specs/policy/secret_gate.t27", "t27b": "pass"}, {"path": "specs/policy/text_lines.t27", "t27b": "pass"}, {"path": "specs/port/bootstrap/src/bitnet_irq.t27", "t27b": "pass"}, {"path": "specs/port/bootstrap/src/bridge.t27", "t27b": "pass"}, @@ -1987,7 +1993,7 @@ {"path": "specs/tri/utils/terminal.t27", "t27b": "blocked", "blocker": "ExprCall(@import)", "reason": "unimplemented"}, {"path": "specs/tri/utils/text.t27", "t27b": "pass"}, {"path": "specs/tri/utils/time.t27", "t27b": "blocked", "blocker": "ExprCall(@divFloor)", "reason": "unimplemented"}, - {"path": "specs/tri/utils/utf8.t27", "t27b": "blocked", "blocker": "ExprCall(@truncate)", "reason": "unimplemented"}, + {"path": "specs/tri/utils/utf8.t27", "t27b": "pass"}, {"path": "specs/tri/utils/version.t27", "t27b": "pass"}, {"path": "specs/trinet/eth_top_ax7203.t27", "t27b": "pass"}, {"path": "specs/trinet/etx.t27", "t27b": "pass"}, From 893c92916daaa295fc8c87ed5bfc458432f24ee5 Mon Sep 17 00:00:00 2001 From: Dmitrii Vasilev Date: Mon, 19 Oct 2026 14:21:04 +0700 Subject: [PATCH 16/21] t27f: re-save the idea_factory seal on the lab after the master merge, 14/14 pass, MATCH (Refs #10815) Co-Authored-By: Claude Opus 5.5 --- .trinity/seals/t27f_idea_factory.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.trinity/seals/t27f_idea_factory.json b/.trinity/seals/t27f_idea_factory.json index 90cd33d12e..f6d9cd564a 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:ddc41e93c87c1268b2d075c6b34fba3208ac77bc2db7992ee9430b219f3aa2f1", - "gen_hash_rust": "sha256:dfb079933e8ffe3343e2360a23e71f9a92bc7c27b20a2e11c6659f95e1562c63", - "gen_hash_verilog": "sha256:029af4d7206071497f23fd5b12d15eb47df379d6f508d2f9a192edfb12337578", - "gen_hash_zig": "sha256:27984b39231077b1f334e7d6fe2f2249667ffb6838f0bf21f4b8e38861e98fd7", + "gen_hash_c": "sha256:bb1ef6081add1e9fa249288e6ebd223bc66be7f13d91210eed6dac48ed4a01aa", + "gen_hash_rust": "sha256:2420620e8b22b844cf0912ec4a21d7358a0045cec3508d8a14c6d8206f169ccb", + "gen_hash_verilog": "sha256:8f8cd1625c7f3d516bc5e597c4860b81f2a4860ce6d6f3cb4b06c2578568a49d", + "gen_hash_zig": "sha256:b4136a42488a6715a58f8a2e2e482871209c94a0009a78cab7a1f357db42c0d2", "module": "idea_factory", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-19T06:20:13Z", + "sealed_at": "2026-10-19T07:20:44Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:26ab3c6cae74c0c62124efb321098e184703f4b05f1e53fc902fa209dd036768", + "spec_hash": "sha256:a68fbc93ea94b32e264ae91b82b149eb7530c3af4e2787c8b457a2eeec0c3482", "spec_path": "specs/t27f/idea_factory.t27", "tests": { "failed": 0, "failing": [], "forced": false, - "passed": 13, - "total": 13, + "passed": 14, + "total": 14, "vacuous": 0 }, "toolchain": { From ba3bd654febf5aa8d2cde758990ba54b1f28f35f Mon Sep 17 00:00:00 2001 From: Dmitrii Vasilev Date: Mon, 19 Oct 2026 14:22:54 +0700 Subject: [PATCH 17/21] t27f: re-save the idea_factory seal on the lab after the merge, 17/17 pass, MATCH (Refs #10820) Co-Authored-By: Claude Opus 5.5 --- .trinity/seals/t27f_idea_factory.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.trinity/seals/t27f_idea_factory.json b/.trinity/seals/t27f_idea_factory.json index f6d9cd564a..836fec9e7e 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:bb1ef6081add1e9fa249288e6ebd223bc66be7f13d91210eed6dac48ed4a01aa", - "gen_hash_rust": "sha256:2420620e8b22b844cf0912ec4a21d7358a0045cec3508d8a14c6d8206f169ccb", - "gen_hash_verilog": "sha256:8f8cd1625c7f3d516bc5e597c4860b81f2a4860ce6d6f3cb4b06c2578568a49d", - "gen_hash_zig": "sha256:b4136a42488a6715a58f8a2e2e482871209c94a0009a78cab7a1f357db42c0d2", + "gen_hash_c": "sha256:b4cb42ec55deec10b3180769d04e36217f39dc786c7756387880cf26bc7f3f15", + "gen_hash_rust": "sha256:7632796cc4ee12e7d310e8115091e72248074a2e25ff02e8773ea5776ec6b2d1", + "gen_hash_verilog": "sha256:a1e8068e1df97e43a750658c626f76e47f52b7d62f249bc598af057a78a9e35f", + "gen_hash_zig": "sha256:6b2f7675bb3198c6045686c29eac989768cb5d3e0b00b14795be854be56a8c9f", "module": "idea_factory", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-19T07:20:44Z", + "sealed_at": "2026-10-19T07:22:33Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:a68fbc93ea94b32e264ae91b82b149eb7530c3af4e2787c8b457a2eeec0c3482", + "spec_hash": "sha256:44432d34553ebeb531b415544066d45f5928975c5a98fb95aa7bd69267430a5a", "spec_path": "specs/t27f/idea_factory.t27", "tests": { "failed": 0, "failing": [], "forced": false, - "passed": 14, - "total": 14, + "passed": 17, + "total": 17, "vacuous": 0 }, "toolchain": { From 7b2dcb03ff6bf1bb6c66a229a46cc35b0bc2c816 Mon Sep 17 00:00:00 2001 From: Dmitrii Vasilev Date: Mon, 19 Oct 2026 14:24:10 +0700 Subject: [PATCH 18/21] t27f: re-save the idea_factory seal on the lab after the merge, MATCH (Refs #10828) Co-Authored-By: Claude Opus 5.5 --- .trinity/seals/t27f_idea_factory.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.trinity/seals/t27f_idea_factory.json b/.trinity/seals/t27f_idea_factory.json index 836fec9e7e..35f40ce532 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:22:33Z", + "sealed_at": "2026-10-19T07:24:00Z", "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": { From 18f9209037128e06e471f26a44ad636bb2dc35bd Mon Sep 17 00:00:00 2001 From: Dmitrii Vasilev Date: Mon, 19 Oct 2026 14:37:35 +0700 Subject: [PATCH 19/21] t27f: re-seal idea_factory after the merge with master (Refs #10820) Saved and verified on the Railway lab: test-report 0 FAIL, 17 tests, 0 vacuous; seal --verify MATCH. Co-Authored-By: Claude Opus 5.5 --- .trinity/seals/t27f_idea_factory.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.trinity/seals/t27f_idea_factory.json b/.trinity/seals/t27f_idea_factory.json index f6d9cd564a..ef63bbe1aa 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:bb1ef6081add1e9fa249288e6ebd223bc66be7f13d91210eed6dac48ed4a01aa", - "gen_hash_rust": "sha256:2420620e8b22b844cf0912ec4a21d7358a0045cec3508d8a14c6d8206f169ccb", - "gen_hash_verilog": "sha256:8f8cd1625c7f3d516bc5e597c4860b81f2a4860ce6d6f3cb4b06c2578568a49d", - "gen_hash_zig": "sha256:b4136a42488a6715a58f8a2e2e482871209c94a0009a78cab7a1f357db42c0d2", + "gen_hash_c": "sha256:b4cb42ec55deec10b3180769d04e36217f39dc786c7756387880cf26bc7f3f15", + "gen_hash_rust": "sha256:7632796cc4ee12e7d310e8115091e72248074a2e25ff02e8773ea5776ec6b2d1", + "gen_hash_verilog": "sha256:a1e8068e1df97e43a750658c626f76e47f52b7d62f249bc598af057a78a9e35f", + "gen_hash_zig": "sha256:6b2f7675bb3198c6045686c29eac989768cb5d3e0b00b14795be854be56a8c9f", "module": "idea_factory", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-19T07:20:44Z", + "sealed_at": "2026-10-19T07:36:57Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:a68fbc93ea94b32e264ae91b82b149eb7530c3af4e2787c8b457a2eeec0c3482", + "spec_hash": "sha256:44432d34553ebeb531b415544066d45f5928975c5a98fb95aa7bd69267430a5a", "spec_path": "specs/t27f/idea_factory.t27", "tests": { "failed": 0, "failing": [], "forced": false, - "passed": 14, - "total": 14, + "passed": 17, + "total": 17, "vacuous": 0 }, "toolchain": { From 1f48803ed2b62e3bc519b29ca95641c9f3161a06 Mon Sep 17 00:00:00 2001 From: Dmitrii Vasilev Date: Mon, 19 Oct 2026 14:39:16 +0700 Subject: [PATCH 20/21] t27f: re-seal idea_factory after the merge with preflight and master (Refs #10828) Saved and verified on the Railway lab: test-report 0 FAIL, 18 tests, 0 vacuous; seal --verify MATCH. Co-Authored-By: Claude Opus 5.5 --- .trinity/seals/t27f_idea_factory.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.trinity/seals/t27f_idea_factory.json b/.trinity/seals/t27f_idea_factory.json index ef63bbe1aa..12388d3361 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:38:53Z", "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": { From 7f2bc73538baa3e8d6d54c1a12561fc8741bc241 Mon Sep 17 00:00:00 2001 From: Dmitrii Vasilev Date: Mon, 19 Oct 2026 14:53:42 +0700 Subject: [PATCH 21/21] t27f: re-seal idea_factory after the merge with master (Refs #10828) Saved and verified on the Railway lab: test-report 0 FAIL, 18 tests, 0 vacuous; seal --verify MATCH. Co-Authored-By: Claude Opus 5.5 --- .trinity/seals/t27f_idea_factory.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) 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": {