From 3bb0e893f2c2361973f8510312c21e99da409702 Mon Sep 17 00:00:00 2001 From: Dmitrii Vasilev Date: Sat, 17 Oct 2026 23:12:56 +0700 Subject: [PATCH] spec(t27f): a research reel through the same topic workflow -- GF16 vs bfloat16 NMSE (Closes #10010) - topic_engine: keyword_hits + route_brief execute KEYWORD_MATCH on a brief's own text; no reel counts hits by hand any more. - topic_reel (new): the one workflow every topic reel calls -- statuses_earned, gaps_are_named, slides_fit, stage_slides_bare, counts. - reel_t27_basics: data plus course-specific checks; its local copies of the counting and slide-fit functions are gone. - reel_gf16_nmse (new): second domain, research. 27 steps from the NMSE protocol spec, doc, schema and PUB-01/PUB-02; 15 published, 1 verified, 6 drafts, 5 named gaps (the sealed run PUB-02 asks for does not exist). t27c and t27b agree on every test of all seven touched or dependent specs, and on 10 planted faults. Refs gHashTag/trinity#1677 Co-Authored-By: Claude Opus 5.5 --- specs/t27f/reel_gf16_nmse.t27 | 288 +++++++++++++++++++++++++++++++++ specs/t27f/reel_t27_basics.t27 | 149 ++++------------- specs/t27f/topic_engine.t27 | 123 +++++++++++++- specs/t27f/topic_reel.t27 | 241 +++++++++++++++++++++++++++ 4 files changed, 674 insertions(+), 127 deletions(-) create mode 100644 specs/t27f/reel_gf16_nmse.t27 create mode 100644 specs/t27f/topic_reel.t27 diff --git a/specs/t27f/reel_gf16_nmse.t27 b/specs/t27f/reel_gf16_nmse.t27 new file mode 100644 index 0000000000..31bb7a7f75 --- /dev/null +++ b/specs/t27f/reel_gf16_nmse.t27 @@ -0,0 +1,288 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/t27f/reel_gf16_nmse.t27 -- the second real T27F reel, and the first in another domain: +; research, "how close does GF16 come to bfloat16", as the project's own NMSE protocol asks it. +; gHashTag/trinity#1677 wants at least two very different domains on one widget and one workflow, +; and a 27-step map that is source-grounded with its gaps named. The first reel +; (reel_t27_basics.t27) is a course, routed to engineering; this one is a benchmark, routed to +; research, and both go through the same code: topic_engine.t27 decides, topic_reel.t27 checks, +; reel.t27 and deck.t27 draw. Nothing below is a rule of this file's own. + +; SOURCE: gHashTag/t27 at SOURCE_COMMIT, measured on MEASURED_ON. The protocol spec +; specs/benchmarks/gf16_bfloat16_nmse.t27 and its mirror docs/GF16_BFLOAT16_NMSE_PROTOCOL.md, the +; manifest schema schemas/nmse-protocol-v1.json, the roadmap docs/SCIENTIFIC_IMPROVEMENT_PLAN.md +; (PUB-01, PUB-02), and what the repo already holds about GF16. Every cell names its source in +; SOURCES; a cell with none is a gap and says in MISSING what would ground it. + +; BRIEF is the first three sentences of the protocol spec's header, verbatim. route_brief +; counts gf16 twice and benchmark once, no other list is hit, so the domain is DOMAIN_RESEARCH. + +; LENSES: one row per lens. Steps 1-9 are the formats and why they compare at all; 10-18 the protocol +; and the one run it asks for; 19-27 what may be said and what the repo has said so far. + +; MEASURED on 2026-10-17: +; - grounded: the source is in the repo at SOURCE_COMMIT (path and section in SOURCES). +; - test: t27c test-report and t27b test --check on the Railway lab passed all 7 tests of the +; protocol spec; a planted GF16_MANT_BITS = 8 turned 2 of them red under both, so they are real. +; - review: the PRs that wrote the protocol (#1399) and the spec rewrite (#5947) have 0 reviews. +; - live: GET https://t27.ai/t27/files/ returned 200 for the protocol spec and the schema, +; and 404 for the doc, FORMAT-SPEC-001.json, gf16_bench_results.json, the crosswalk and the paper +; draft; a made-up path returned 404, so 200 is not a catch-all. +; The engine asks a research LEARN step for a source link, a PRACTICE step for a review or a passing +; test, and a DELIVER step for a review. So: 15 published (sourced, earned, and live), 1 verified +; (reporting rules: sourced, not live), 6 drafts (practice or deliver steps with neither a test nor a +; review) and 5 gaps. + +; THE GAPS are the point of this reel. PUB-02 asks for one sealed bench/results/nmse-*.json; the repo +; holds none, so the run, its schema check, the ratio per distribution and the paper's Delta_dB (which +; PUB-02 forbids before that manifest) are gaps, and so is the chip repo's manifest: +; gHashTag/tt-trinity-gamma tests/vectors/nmse_gf16_bf16.golden.json at GAMMA_COMMIT pins the seeds +; but says measured_silicon false and harness_committed false. The only GF16/BF16 numbers in the repo, +; conformance/gf16_bench_results.json (2026-04-05, no seed, no seal), are drawn as a draft that the +; protocol itself says does not count. + +; Not claimed: no NMSE value, no ratio, no winner. No GIF or MP4; stills only, for the chat. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module reel_gf16_nmse; + +use t27f::reel::CELL_COUNT; +use t27f::reel::DEMO_COUNT; +use t27f::reel::DEMO_CELL; +use t27f::reel::DEMO_LENS; +use t27f::topic_engine::STATUS_GAP; +use t27f::topic_engine::STATUS_DRAFT; +use t27f::topic_engine::STATUS_VERIFIED; +use t27f::topic_engine::STATUS_PUBLISHED; +use t27f::topic_engine::STAGE_DELIVER; +use t27f::topic_engine::STAGE_PRACTICE; +use t27f::topic_engine::DOMAIN_RESEARCH; +use t27f::topic_engine::CONTENT_WORDS; +use t27f::topic_engine::ENGINEERING_WORDS; +use t27f::topic_engine::RESEARCH_WORDS; +use t27f::topic_engine::SALES_WORDS; +use t27f::topic_engine::keyword_hits; +use t27f::topic_engine::route_brief; +use t27f::topic_engine::step_stage; +use t27f::topic_reel::count_status; +use t27f::topic_reel::count_true; +use t27f::topic_reel::statuses_earned; +use t27f::topic_reel::gaps_are_named; +use t27f::topic_reel::slides_fit; +use t27f::topic_reel::stage_slides_bare; + +pub const TOPIC_ID : str = "gf16-nmse"; +pub const SOURCE_REPO : str = "gHashTag/t27"; +pub const SOURCE_SPEC : str = "specs/benchmarks/gf16_bfloat16_nmse.t27"; +pub const SOURCE_COMMIT : str = "de2cff88243c179e5bbf21f8cb06e5da1ab833e8"; +pub const GAMMA_REPO : str = "gHashTag/tt-trinity-gamma"; +pub const GAMMA_COMMIT : str = "de484c4ff249356624ec508127fcbab880ee25fc"; +pub const LIVE_BASE : str = "https://t27.ai/t27/files/"; +pub const MEASURED_ON : str = "2026-10-17"; + +pub const BRIEF : str = "GF16 against bfloat16 NMSE protocol (TRI-NET benchmark 0). The standard comparison of GoldenFloat GF16 and bfloat16 numeric fidelity, as normalised mean squared error over named reference distributions. Protocol only: no silicon number is asserted."; +/// The domain route_brief gives that brief (see the_domain_is_routed_not_chosen). +pub const DOMAIN : u8 = 3; + +pub const LENS_TITLES : [3]str = ["formats", "protocol", "claims"]; + +pub const TITLES : [27]str = [ + "NMSE, the measure", + "GF16: 1 | 6 | 9, bias 31", + "bfloat16: 1 | 8 | 7, bias 127", + "Both are 16 bits", + "Each bias is half the exponent range", + "GF16 trades exponent for mantissa", + "Why the same width makes it fair", + "Where P3109 places both", + "GF16 in the numeric SSOT", + "Five reference distributions", + "Check phi before any NMSE", + "What a manifest holds", + "The witness holds in f64", + "D_DEEP weights sum to one", + "NMSE is never negative", + "The sealed run, 5 x 10M samples", + "A manifest checked against its schema", + "The chip repo's manifest", + "Reading the ratio", + "Reporting rules", + "What is not claimed", + "Which way the ratio points", + "The old numbers, 2026-04-05", + "Why they do not count", + "The ratio per distribution", + "Delta_dB in the paper", + "The paper draft" +]; + +/// Where each cell's text comes from in SOURCE_REPO at SOURCE_COMMIT; "" is a gap. +pub const SOURCES : [27]str = [ + "specs/benchmarks/gf16_bfloat16_nmse.t27 header, NMSE(F)", + "specs/benchmarks/gf16_bfloat16_nmse.t27 GF16_*", + "specs/benchmarks/gf16_bfloat16_nmse.t27 BF16_*", + "specs/benchmarks/gf16_bfloat16_nmse.t27 test both_layouts_are_sixteen_bits", + "specs/benchmarks/gf16_bfloat16_nmse.t27 test biases_are_half_the_exponent_range", + "specs/benchmarks/gf16_bfloat16_nmse.t27 test gf16_trades_exponent_for_mantissa", + "docs/GF16_BFLOAT16_NMSE_PROTOCOL.md section 3.3", + "docs/P3109_CROSSWALK_4PARAM.md rows bf16 and gf16", + "conformance/FORMAT-SPEC-001.json", + "specs/benchmarks/gf16_bfloat16_nmse.t27 DISTRIBUTIONS", + "specs/benchmarks/gf16_bfloat16_nmse.t27 WITNESS_TOLERANCE", + "schemas/nmse-protocol-v1.json", + "specs/benchmarks/gf16_bfloat16_nmse.t27 test identity_witnesses_hold_in_f64", + "specs/benchmarks/gf16_bfloat16_nmse.t27 test d_deep_mixture_weights_sum_to_one", + "specs/benchmarks/gf16_bfloat16_nmse.t27 test nmse_non_negative_and_zero_baseline_undefined", + "", + "", + "", + "specs/benchmarks/gf16_bfloat16_nmse.t27 header, the ratio", + "docs/GF16_BFLOAT16_NMSE_PROTOCOL.md section 8", + "specs/benchmarks/gf16_bfloat16_nmse.t27 NON_CLAIMS", + "specs/benchmarks/gf16_bfloat16_nmse.t27 test ratio_direction", + "conformance/gf16_bench_results.json", + "docs/GF16_BFLOAT16_NMSE_PROTOCOL.md section 6", + "", + "", + "docs/WHITEPAPER/gf_paper_v3_imrad_draft.md (PUB-01)" +]; + +/// What would ground each gap; "" for a cell that has its source. +pub const MISSING : [27]str = [ + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + "bench/results/nmse-*.json, the sealed run PUB-02 asks for: the repo holds 0 such files", + "a manifest to check against schemas/nmse-protocol-v1.json: none exists", + "tt-trinity-gamma nmse_gf16_bf16.golden.json pins seeds only: measured_silicon false, harness_committed false", + "", "", "", "", "", "", + "an NMSE ratio per distribution: no run has produced one", + "a Delta_dB figure: PUB-02 forbids one before the manifest lands", + "" +]; + +/// The measurements per cell (see the header for how each was taken). +pub const GROUNDED : [27]bool = [true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, false, false, false, true, true, true, true, true, true, false, false, true]; +pub const TEST_PASS : [27]bool = [false, false, false, true, true, true, false, false, false, false, false, false, true, true, true, false, false, false, false, false, false, true, false, false, false, false, false]; +pub const REVIEWED : [27]bool = [false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false]; +pub const LIVE : [27]bool = [true, true, true, true, true, true, false, false, false, true, true, true, true, true, true, false, false, false, true, false, true, true, false, false, false, false, false]; + +/// What a host passes the viewer, per cell: the status code and the step number as a label. +pub const STATUS : [27]u8 = [5, 5, 5, 5, 5, 5, 3, 3, 3, 5, 5, 5, 5, 5, 5, 0, 0, 0, 5, 4, 5, 5, 3, 3, 0, 0, 3]; +pub const LABELS : [27]str = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27"]; + +/// Slide text per demo scene, checked by topic_reel.t27 slides_fit (index scene*4+row). +pub const HEAD : [5]str = [ + "GF16 vs bfloat16 NMSE", + "Steps 1-9: formats", + "Witness first", + "Evidence", + "The missing run" +]; +pub const ROWS : [20]str = [ + "", "", "", "", + "", "", "", "", + "Step 11 of 27: check phi before NMSE", + "phi^2 + 1/phi^2 = 3 within 1e-15", + "A failed witness aborts the run", + "No manifest is written after a failure", + "7 of 7 protocol tests pass on the lab", + "15 published, 1 verified, 6 drafts", + "5 gaps: no measured run exists yet", + "Measured 2026-10-17", + "PUB-02: one sealed NMSE manifest", + "5 distributions, 10M samples each", + "Gaps stay gaps until a run lands", + "" +]; + +/// The protocol spec's own test count, which the evidence slide quotes. +pub const PROTOCOL_TESTS : i32 = 7; + +pub fn step_number(cell: i32) -> i32 { + return cell + 1; +} + +test the_domain_is_routed_not_chosen { + assert DOMAIN == DOMAIN_RESEARCH; + // gf16, gf16, benchmark; "bfloat16" is not "gf16", and no other list is hit + assert keyword_hits(BRIEF, BRIEF.len, RESEARCH_WORDS, RESEARCH_WORDS.len) == 3; + assert keyword_hits(BRIEF, BRIEF.len, CONTENT_WORDS, CONTENT_WORDS.len) == 0; + assert keyword_hits(BRIEF, BRIEF.len, ENGINEERING_WORDS, ENGINEERING_WORDS.len) == 0; + assert keyword_hits(BRIEF, BRIEF.len, SALES_WORDS, SALES_WORDS.len) == 0; + assert route_brief(BRIEF, BRIEF.len) == DOMAIN; +} + +test every_step_is_a_cell { + assert CELL_COUNT == 27; + var c : i32 = 0; + while (c < CELL_COUNT) { + const i : usize = @intCast(c); + assert TITLES[i].len > 0; + var want : i32 = 1; + if (step_number(c) >= 10) { + want = 2; + } + const len : i32 = @intCast(LABELS[i].len); + assert len == want; + c = c + 1; + } + assert LENS_TITLES[2].len > 0; +} + +test a_gap_is_named_not_filled { + assert gaps_are_named(SOURCES, MISSING, GROUNDED); + assert count_true(GROUNDED) == 22; + // a gap is drawn as a gap, and only a gap is + var c : i32 = 0; + while (c < CELL_COUNT) { + const i : usize = @intCast(c); + assert (STATUS[i] == STATUS_GAP) == (GROUNDED[i] == false); + c = c + 1; + } + // every gap is a DELIVER step: what is missing is the run and what follows it, not the protocol + assert step_stage(15) == STAGE_DELIVER; + assert step_stage(16) == STAGE_DELIVER; + assert step_stage(17) == STAGE_DELIVER; + assert step_stage(24) == STAGE_DELIVER; + assert step_stage(25) == STAGE_DELIVER; +} + +test a_status_is_earned_not_chosen { + assert statuses_earned(STATUS, DOMAIN, GROUNDED, TEST_PASS, REVIEWED, LIVE); + // the evidence slide's "15 published, 1 verified, 6 drafts" and "5 gaps" + assert count_status(STATUS, STATUS_PUBLISHED) == 15; + assert count_status(STATUS, STATUS_VERIFIED) == 1; + assert count_status(STATUS, STATUS_DRAFT) == 6; + assert count_status(STATUS, STATUS_GAP) == 5; + assert count_true(REVIEWED) == 0; + // every passing test is one of the protocol's own tests, on a PRACTICE step + assert count_true(TEST_PASS) == PROTOCOL_TESTS; + var c : i32 = 0; + while (c < CELL_COUNT) { + const i : usize = @intCast(c); + if (TEST_PASS[i]) { + assert step_stage(@intCast(c)) == STAGE_PRACTICE; + } + c = c + 1; + } + // the old numbers have a source but no seed, seal, test or review: a draft, never published + assert STATUS[22] == STATUS_DRAFT; +} + +test the_camera_is_the_demo_camera { + assert DEMO_COUNT == 5; + // scene 1 shows lens 0, the row HEAD[1] calls "Steps 1-9" + assert DEMO_LENS[1] == 0; + // scenes 2 and 3 focus cell 10, which the focus slide calls "Step 11": the witness + assert DEMO_CELL[2] == 10; + assert DEMO_CELL[3] == 10; + assert step_number(DEMO_CELL[2]) == 11; + assert STATUS[10] == STATUS_PUBLISHED; +} + +test every_slide_fits_every_preset { + assert slides_fit(HEAD, ROWS); + assert stage_slides_bare(ROWS); +} + +invariant twenty_seven_steps { + assert CELL_COUNT == 27; +} diff --git a/specs/t27f/reel_t27_basics.t27 b/specs/t27f/reel_t27_basics.t27 index cc9225a33c..e034f749e6 100644 --- a/specs/t27f/reel_t27_basics.t27 +++ b/specs/t27f/reel_t27_basics.t27 @@ -13,10 +13,15 @@ ; 10-18 (tests and functions, inside a function, your own types), 19-27 (modules and rules, the ; compiler and tri, errors and what next). The test lenses_are_whole_modules holds that. +; WORKFLOW: this file is data. The checks it runs on that data are the shared ones in +; specs/t27f/topic_reel.t27 (statuses_earned, slides_fit, stage_slides_bare), which the research +; reel reel_gf16_nmse.t27 runs too; only the checks about this course stay here. + ; STATUS is earned through the topic engine (specs/t27f/topic_engine.t27, earned_status), not -; chosen here. The engine routes the course brief (its TITLE and DESCRIPTION at SOURCE_COMMIT) by -; KEYWORD_MATCH: spec, compiler, fpga are 3 engineering hits and no other list is hit, so the domain -; is DOMAIN_ENGINEERING. What was measured, per lesson, on 2026-10-17: +; chosen here. The engine routes BRIEF (the source's TITLE and DESCRIPTION at SOURCE_COMMIT) with +; route_brief, which counts the KEYWORD_MATCH hits itself: spec, compiler, fpga are 3 engineering +; hits and no other list is hit, so the domain is DOMAIN_ENGINEERING. Until 2026-10-17 the hits were +; counted by hand and written down here. What was measured, per lesson, on 2026-10-17: ; - grounded: every lesson has its text in the source spec, so every one has a source link. ; - spec: t27c test-report on the Railway lab passed every test of all 27 lesson specs ; (apps/website/public/t27/files/specs/basics/*.t27 at SOURCE_COMMIT). 23 of them report their @@ -50,33 +55,24 @@ module reel_t27_basics; use t27f::reel::CELL_COUNT; use t27f::reel::COLS; use t27f::reel::DEMO_COUNT; -use t27f::reel::DEMO_KIND; use t27f::reel::DEMO_CELL; use t27f::reel::DEMO_LENS; -use t27f::reel::PRESET_COUNT; use t27f::reel::STYLE_PUBLISHED; use t27f::reel::cell_row; use t27f::reel::cell_style; -use t27f::deck::LAYOUT_SPLIT; -use t27f::deck::LAYOUT_STAGE; -use t27f::deck::REGION_TITLE; -use t27f::deck::HEADLINE_PX; -use t27f::deck::BODY_PX; -use t27f::deck::HEADLINE_EM_PERMILLE; -use t27f::deck::BODY_EM_PERMILLE; -use t27f::deck::MAX_ROWS; -use t27f::deck::layout_for; -use t27f::deck::text_fits; -use t27f::deck::chars_per_line; -use t27f::deck::card_inner_w; -use t27f::deck::region_w; use t27f::topic_engine::STATUS_DRAFT; use t27f::topic_engine::STATUS_PUBLISHED; use t27f::topic_engine::STAGE_DELIVER; use t27f::topic_engine::DOMAIN_ENGINEERING; -use t27f::topic_engine::route; +use t27f::topic_engine::ENGINEERING_WORDS; +use t27f::topic_engine::keyword_hits; +use t27f::topic_engine::route_brief; use t27f::topic_engine::step_stage; -use t27f::topic_engine::earned_status; +use t27f::topic_reel::count_status; +use t27f::topic_reel::count_true; +use t27f::topic_reel::statuses_earned; +use t27f::topic_reel::slides_fit; +use t27f::topic_reel::stage_slides_bare; pub const TOPIC_ID : str = "t27-basics"; pub const SOURCE_REPO : str = "gHashTag/trinity"; @@ -88,9 +84,10 @@ pub const MEASURED_ON : str = "2026-10-17"; pub const LESSONS_PER_MODULE : i32 = 3; pub const MODULES_PER_LENS : i32 = 3; -/// The domain the engine routes the course brief to, and the hits it counted (see the header). +/// The brief the engine routes: the source's TITLE, a space, and its DESCRIPTION, verbatim. +pub const BRIEF : str = "t27 basics: the language in 27 lessons Course 0, 27 lessons, one infographic and one t27 spec each: a module, constants and types, functions and tests, then the compiler's stages and the 7 languages it writes. It comes before the FPGA course."; +/// The domain route_brief gives that brief (see the_domain_is_routed_not_chosen). pub const DOMAIN : u8 = 2; -pub const ENGINEERING_HITS : u32 = 3; /// The measurements per lesson, in lesson order (see the header for how each was taken). pub const GROUNDED : [27]bool = [true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true]; @@ -104,10 +101,8 @@ pub const LABELS : [27]str = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", pub const LENS_TITLES : [3]str = ["values", "logic", "tools"]; -/// Slide text per demo scene. HEAD is the headline, one line: a stage strip holds one line on the wide -/// and square presets, and the host wraps a title greedily by words, so a headline that fits one line -/// on every preset is the only kind the host draws the same everywhere. ROWS holds four card rows per -/// scene (index scene*4+row; an empty row is no row), each short enough never to wrap. +/// Slide text per demo scene. HEAD is the headline, one line; ROWS holds four card rows per scene +/// (index scene*4+row; an empty row is no row). topic_reel.t27 slides_fit says why and checks it. pub const HEAD : [5]str = [ "t27 basics: 27 lessons", "Lessons 1-9: values", @@ -140,60 +135,6 @@ pub fn module_of(cell: i32) -> i32 { return @divTrunc(cell, LESSONS_PER_MODULE); } -/// The status the engine says a cell has earned from its measurements. -pub fn measured_status(cell: i32) -> u8 { - const i : usize = @intCast(cell); - return earned_status(@intCast(cell), DOMAIN, GROUNDED[i], SPEC_PASSES[i], REVIEWED[i], PAGE_LIVE[i]); -} - -pub fn count_status(status: u8) -> i32 { - var n : i32 = 0; - var c : i32 = 0; - while (c < CELL_COUNT) { - if (STATUS[@intCast(c)] == status) { - n = n + 1; - } - c = c + 1; - } - return n; -} - -pub fn count_true(xs: [27]bool) -> i32 { - var n : i32 = 0; - var c : i32 = 0; - while (c < CELL_COUNT) { - if (xs[@intCast(c)]) { - n = n + 1; - } - c = c + 1; - } - return n; -} - -pub fn row_count(scene: i32) -> i32 { - var n : i32 = 0; - var j : i32 = 0; - while (j < MAX_ROWS) { - if (ROWS[@intCast(scene * 4 + j)].len > 0) { - n = n + 1; - } - j = j + 1; - } - return n; -} - -/// Characters a headline line may hold on this scene's slide, as the host breaks it. -pub fn head_chars(layout: u8, preset: u8) -> i32 { - if (layout == LAYOUT_SPLIT) { - return chars_per_line(card_inner_w(preset), HEADLINE_PX[preset], HEADLINE_EM_PERMILLE); - } - return chars_per_line(region_w(layout, preset, REGION_TITLE), HEADLINE_PX[preset], HEADLINE_EM_PERMILLE); -} - -pub fn row_chars(preset: u8) -> i32 { - return chars_per_line(card_inner_w(preset), BODY_PX[preset], BODY_EM_PERMILLE); -} - test every_lesson_is_a_cell { assert CELL_COUNT == 27; assert lesson_number(0) == 1; @@ -225,18 +166,14 @@ test lenses_are_whole_modules { } test a_status_is_earned_not_chosen { - var c : i32 = 0; - while (c < CELL_COUNT) { - assert STATUS[@intCast(c)] == measured_status(c); - c = c + 1; - } + assert statuses_earned(STATUS, DOMAIN, GROUNDED, SPEC_PASSES, REVIEWED, PAGE_LIVE); assert count_true(GROUNDED) == 27; assert count_true(PAGE_LIVE) == 27; assert count_true(SPEC_PASSES) == 27; assert count_true(REVIEWED) == 0; // the evidence slide's "18 published; 9 wait for a review" - assert count_status(STATUS_PUBLISHED) == 18; - assert count_status(STATUS_DRAFT) == 9; + assert count_status(STATUS, STATUS_PUBLISHED) == 18; + assert count_status(STATUS, STATUS_DRAFT) == 9; // the drafts are exactly the DELIVER lessons, whose pages are live all the same var d : i32 = 0; while (d < CELL_COUNT) { @@ -250,7 +187,9 @@ test a_status_is_earned_not_chosen { test the_domain_is_routed_not_chosen { assert DOMAIN == DOMAIN_ENGINEERING; - assert route(0, ENGINEERING_HITS, 0, 0) == DOMAIN; + // spec, compiler, fpga: "tests" is not "test", and no other list is hit + assert keyword_hits(BRIEF, BRIEF.len, ENGINEERING_WORDS, ENGINEERING_WORDS.len) == 3; + assert route_brief(BRIEF, BRIEF.len) == DOMAIN; } test the_camera_is_the_demo_camera { @@ -264,38 +203,8 @@ test the_camera_is_the_demo_camera { } test every_slide_fits_every_preset { - var s : i32 = 0; - while (s < DEMO_COUNT) { - const layout : u8 = layout_for(DEMO_KIND[@intCast(s)]); - assert HEAD[@intCast(s)].len > 0; - var p : u8 = 0; - while (p < PRESET_COUNT) { - // one headline line that the host never breaks, and rows it never breaks either - assert text_fits(1, row_count(s), layout, p); - const head_len : i32 = @intCast(HEAD[@intCast(s)].len); - assert head_len <= head_chars(layout, p); - var j : i32 = 0; - while (j < MAX_ROWS) { - const row_len : i32 = @intCast(ROWS[@intCast(s * 4 + j)].len); - assert row_len <= row_chars(p); - j = j + 1; - } - p = p + 1; - } - s = s + 1; - } -} - -test stage_slides_carry_no_card_text { - // why HEAD is one line: on the wide frame a second stage line would not fit the strip - assert text_fits(2, 0, LAYOUT_STAGE, 0) == false; - var s : i32 = 0; - while (s < DEMO_COUNT) { - if (layout_for(DEMO_KIND[@intCast(s)]) == LAYOUT_STAGE) { - assert row_count(s) == 0; - } - s = s + 1; - } + assert slides_fit(HEAD, ROWS); + assert stage_slides_bare(ROWS); } invariant one_lens_per_row { diff --git a/specs/t27f/topic_engine.t27 b/specs/t27f/topic_engine.t27 index c24cc60f1c..d01c99aea2 100644 --- a/specs/t27f/topic_engine.t27 +++ b/specs/t27f/topic_engine.t27 @@ -62,8 +62,8 @@ pub const DOMAIN_RESEARCH : u8 = 3; pub const DOMAIN_SALES : u8 = 4; pub const DOMAIN_NAMES : str = "general,content,engineering,research,sales"; -/// Keyword vocabularies, comma separated. The host counts how many words of the brief appear in each -/// list and passes the four counts to route(). How a word is matched is KEYWORD_MATCH, not a guess: +/// Keyword vocabularies, comma separated. route_brief() counts how many words of the brief appear in +/// each list (keyword_hits) and routes on the four counts. How a word is matched is KEYWORD_MATCH: /// "List a villa" does not hit "listing", and "sea-view" is the two tokens "sea" and "view". pub const KEYWORD_MATCH : str = "lowercase the brief; split it on every character that is not a-z or 0-9; a token is a hit when it equals a list word exactly; no stems, no plurals; a word repeated in the brief counts each time"; pub const CONTENT_WORDS : str = "blog,post,campaign,reel,video,channel,audience,content,newsletter"; @@ -206,6 +206,96 @@ pub fn route(content_hits: u32, engineering_hits: u32, research_hits: u32, sales return best; } +/// KEYWORD_MATCH, executed, so a host passes the brief and not counts it made up. A byte as the +/// match reads it: ASCII letters lowercased, everything else unchanged. +pub fn kw_lower(c: u8) -> u8 { + if (c >= 65 and c <= 90) { + return c + 32; + } + return c; +} + +/// Whether a byte belongs to a token: a-z or 0-9 once lowercased. +pub fn kw_token_byte(c: u8) -> bool { + const l : u8 = kw_lower(c); + return (l >= 97 and l <= 122) or (l >= 48 and l <= 57); +} + +/// Whether the token brief[s..e] equals the list word words[ws..we], exactly, after lowercasing. +pub fn kw_equal(brief: []const u8, s: usize, e: usize, words: []const u8, ws: usize, we: usize) -> bool { + if (e - s != we - ws) { + return false; + } + var k : usize = 0; + while (k < e - s) { + if (kw_lower(brief[s + k]) != words[ws + k]) { + return false; + } + k += 1; + } + return true; +} + +/// Whether the token brief[s..e] is a word of the comma separated list words[0..wn]. +pub fn kw_in_list(brief: []const u8, s: usize, e: usize, words: []const u8, wn: usize) -> bool { + var ws : usize = 0; + while (ws < wn) { + var we : usize = ws; + var more : bool = true; + while (more) { + if (we >= wn) { + more = false; + } else { + if (words[we] == 44) { + more = false; + } else { + we += 1; + } + } + } + if (kw_equal(brief, s, e, words, ws, we)) { + return true; + } + ws = we + 1; + } + return false; +} + +/// The hits of brief[0..n] in one keyword list: every token that is a list word counts, repeats too. +pub fn keyword_hits(brief: []const u8, n: usize, words: []const u8, wn: usize) -> u32 { + var hits : u32 = 0; + var i : usize = 0; + while (i < n) { + if (kw_token_byte(brief[i])) { + var e : usize = i; + var more : bool = true; + while (more) { + if (e >= n) { + more = false; + } else { + if (kw_token_byte(brief[e])) { + e += 1; + } else { + more = false; + } + } + } + if (kw_in_list(brief, i, e, words, wn)) { + hits += 1; + } + i = e; + } else { + i += 1; + } + } + return hits; +} + +/// Route a brief by its own text: the four hit counts come from KEYWORD_MATCH, not from the caller. +pub fn route_brief(brief: []const u8, n: usize) -> u8 { + return route(keyword_hits(brief, n, CONTENT_WORDS, CONTENT_WORDS.len), keyword_hits(brief, n, ENGINEERING_WORDS, ENGINEERING_WORDS.len), keyword_hits(brief, n, RESEARCH_WORDS, RESEARCH_WORDS.len), keyword_hits(brief, n, SALES_WORDS, SALES_WORDS.len)); +} + /// One more Bee may start only while fewer than MAX_ACTIVE_BEES are working. pub fn dispatch_allowed(active_bees: u8) -> bool { return active_bees < MAX_ACTIVE_BEES; @@ -400,6 +490,21 @@ test routing_picks_one_domain { assert route(0, 0, 2, 2) == DOMAIN_RESEARCH; } +test keyword_match_is_executed { + // lowercased, repeats count, and every non a-z0-9 byte splits + assert keyword_hits("FPGA fpga Fpga", 14, ENGINEERING_WORDS, ENGINEERING_WORDS.len) == 3; + assert keyword_hits("gf16-benchmark", 14, RESEARCH_WORDS, RESEARCH_WORDS.len) == 2; + // exact words only: no stems, no plurals, and a list word inside a token is not a hit + assert keyword_hits("List a sea-view villa", 21, SALES_WORDS, SALES_WORDS.len) == 1; + assert keyword_hits("villas tests bfloat16", 21, SALES_WORDS, SALES_WORDS.len) == 0; + assert keyword_hits("villas tests bfloat16", 21, ENGINEERING_WORDS, ENGINEERING_WORDS.len) == 0; + // the first and the last word of a list both match + assert keyword_hits("blog newsletter", 15, CONTENT_WORDS, CONTENT_WORDS.len) == 2; + assert keyword_hits("", 0, CONTENT_WORDS, CONTENT_WORDS.len) == 0; + assert route_brief("", 0) == DOMAIN_GENERAL; + assert route_brief("Fix the compiler bug", 20) == DOMAIN_ENGINEERING; +} + test dispatch_is_capped { assert dispatch_allowed(0); assert dispatch_allowed(2); @@ -408,9 +513,11 @@ test dispatch_is_capped { } test demo_villa_sale_brief { - // "List a sea-view villa for a client and draft the sale post", counted by KEYWORD_MATCH: villa, - // client, sale -> 3 sales hits ("list" is not "listing"); post -> 1 content hit - var domain : u8 = route(1, 0, 0, 3); + // KEYWORD_MATCH: villa, client, sale -> 3 sales hits ("list" is not "listing"); post -> 1 content + const brief : str = "List a sea-view villa for a client and draft the sale post"; + assert keyword_hits(brief, brief.len, SALES_WORDS, SALES_WORDS.len) == 3; + assert keyword_hits(brief, brief.len, CONTENT_WORDS, CONTENT_WORDS.len) == 1; + var domain : u8 = route_brief(brief, brief.len); assert domain == DOMAIN_SALES; assert required_evidence(STAGE_LEARN, domain) == EVIDENCE_SOURCE_LINK; assert required_evidence(STAGE_PRACTICE, domain) == EVIDENCE_REVIEW; @@ -458,8 +565,10 @@ test bee_goes_to_the_open_lesson_or_the_first_gap { } test demo_gf16_research_topic { - // "How accurate is GF16 against bf16 on a benchmark": gf16, benchmark -> 2 research hits - var domain : u8 = route(0, 0, 2, 0); + // KEYWORD_MATCH: gf16, benchmark -> 2 research hits ("bf16" is in no list) + const brief : str = "How accurate is GF16 against bf16 on a benchmark"; + assert keyword_hits(brief, brief.len, RESEARCH_WORDS, RESEARCH_WORDS.len) == 2; + var domain : u8 = route_brief(brief, brief.len); assert domain == DOMAIN_RESEARCH; assert required_evidence(STAGE_PRACTICE, domain) == EVIDENCE_REVIEW; assert evidence_accepted(STAGE_PRACTICE, domain, EVIDENCE_TEST_PASS); diff --git a/specs/t27f/topic_reel.t27 b/specs/t27f/topic_reel.t27 new file mode 100644 index 0000000000..a5b074dab9 --- /dev/null +++ b/specs/t27f/topic_reel.t27 @@ -0,0 +1,241 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/t27f/topic_reel.t27 -- the one workflow every T27F topic reel goes through. +; gHashTag/trinity#1677 asks that two very different domains use the same widget and workflow +; "without duplicated orchestration code". A topic reel (reel_t27_basics.t27, reel_gf16_nmse.t27) +; is data: a brief, 27 cells with their measurements, and the slide text. Everything a reel decides +; or checks with that data lives here, once: +; - the domain is route_brief of the brief's own text (topic_engine.t27, KEYWORD_MATCH); +; - every cell's STATUS is earned_status of its four measurements (statuses_earned); +; - a gap is a cell with no source, and it names what is missing (gaps_are_named); +; - every slide's text fits every preset as the host breaks it (slides_fit, stage_slides_bare). +; A reel calls these from its tests and keeps only the checks that are about its own topic. +; Before 2026-10-17 the first reel carried its own copy of the counting and slide-fit functions; a +; second reel would have copied them again, which is the duplication #1677 rules out. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module topic_reel; + +use t27f::reel::CELL_COUNT; +use t27f::reel::DEMO_COUNT; +use t27f::reel::DEMO_KIND; +use t27f::reel::PRESET_COUNT; +use t27f::deck::LAYOUT_SPLIT; +use t27f::deck::LAYOUT_STAGE; +use t27f::deck::REGION_TITLE; +use t27f::deck::HEADLINE_PX; +use t27f::deck::BODY_PX; +use t27f::deck::HEADLINE_EM_PERMILLE; +use t27f::deck::BODY_EM_PERMILLE; +use t27f::deck::MAX_ROWS; +use t27f::deck::layout_for; +use t27f::deck::text_fits; +use t27f::deck::chars_per_line; +use t27f::deck::card_inner_w; +use t27f::deck::region_w; +use t27f::topic_engine::STATUS_GAP; +use t27f::topic_engine::STATUS_DRAFT; +use t27f::topic_engine::STATUS_VERIFIED; +use t27f::topic_engine::DOMAIN_RESEARCH; +use t27f::topic_engine::earned_status; + +/// Slide rows per demo scene: a reel's ROWS holds DEMO_COUNT * ROWS_PER_SCENE entries, scene-major. +pub const ROWS_PER_SCENE : i32 = 4; + +pub fn count_status(status: [27]u8, s: u8) -> i32 { + var n : i32 = 0; + var c : i32 = 0; + while (c < CELL_COUNT) { + if (status[@intCast(c)] == s) { + n = n + 1; + } + c = c + 1; + } + return n; +} + +pub fn count_true(xs: [27]bool) -> i32 { + var n : i32 = 0; + var c : i32 = 0; + while (c < CELL_COUNT) { + if (xs[@intCast(c)]) { + n = n + 1; + } + c = c + 1; + } + return n; +} + +/// The first cell whose drawn status is not the one the engine says its measurements earned, or -1. +pub fn first_unearned(status: [27]u8, domain: u8, grounded: [27]bool, test_pass: [27]bool, reviewed: [27]bool, live: [27]bool) -> i32 { + var c : i32 = 0; + while (c < CELL_COUNT) { + const i : usize = @intCast(c); + if (status[i] != earned_status(@intCast(c), domain, grounded[i], test_pass[i], reviewed[i], live[i])) { + return c; + } + c = c + 1; + } + return -1; +} + +pub fn statuses_earned(status: [27]u8, domain: u8, grounded: [27]bool, test_pass: [27]bool, reviewed: [27]bool, live: [27]bool) -> bool { + return first_unearned(status, domain, grounded, test_pass, reviewed, live) == -1; +} + +/// A gap is named, not blank: a cell is grounded exactly when it has a source, and a cell with no +/// source says what is missing instead. +pub fn gaps_are_named(sources: [27]str, missing: [27]str, grounded: [27]bool) -> bool { + var c : i32 = 0; + while (c < CELL_COUNT) { + const i : usize = @intCast(c); + const has_source : bool = sources[i].len > 0; + const has_missing : bool = missing[i].len > 0; + if (grounded[i] != has_source) { + return false; + } + if (has_missing == has_source) { + return false; + } + c = c + 1; + } + return true; +} + +pub fn row_count(rows: [20]str, scene: i32) -> i32 { + var n : i32 = 0; + var j : i32 = 0; + while (j < MAX_ROWS) { + if (rows[@intCast(scene * ROWS_PER_SCENE + j)].len > 0) { + n = n + 1; + } + j = j + 1; + } + return n; +} + +/// Characters a headline line may hold on this scene's slide, as the host breaks it. +pub fn head_chars(layout: u8, preset: u8) -> i32 { + if (layout == LAYOUT_SPLIT) { + return chars_per_line(card_inner_w(preset), HEADLINE_PX[preset], HEADLINE_EM_PERMILLE); + } + return chars_per_line(region_w(layout, preset, REGION_TITLE), HEADLINE_PX[preset], HEADLINE_EM_PERMILLE); +} + +pub fn row_chars(preset: u8) -> i32 { + return chars_per_line(card_inner_w(preset), BODY_PX[preset], BODY_EM_PERMILLE); +} + +/// Every demo slide fits every preset: one headline line the host never breaks, and card rows it +/// never breaks either. A headline must be one line because the host wraps a title greedily by +/// words, and only a one-line headline is drawn the same on every preset. +pub fn slides_fit(head: [5]str, rows: [20]str) -> bool { + var s : i32 = 0; + while (s < DEMO_COUNT) { + const layout : u8 = layout_for(DEMO_KIND[@intCast(s)]); + if (head[@intCast(s)].len == 0) { + return false; + } + var p : u8 = 0; + while (p < PRESET_COUNT) { + if (text_fits(1, row_count(rows, s), layout, p) == false) { + return false; + } + const head_len : i32 = @intCast(head[@intCast(s)].len); + if (head_len > head_chars(layout, p)) { + return false; + } + var j : i32 = 0; + while (j < MAX_ROWS) { + const row_len : i32 = @intCast(rows[@intCast(s * ROWS_PER_SCENE + j)].len); + if (row_len > row_chars(p)) { + return false; + } + j = j + 1; + } + p = p + 1; + } + s = s + 1; + } + return true; +} + +/// A stage slide (title or overview) carries its headline on the strip and no card rows. +pub fn stage_slides_bare(rows: [20]str) -> bool { + var s : i32 = 0; + while (s < DEMO_COUNT) { + if (layout_for(DEMO_KIND[@intCast(s)]) == LAYOUT_STAGE and row_count(rows, s) > 0) { + return false; + } + s = s + 1; + } + return true; +} + +/// Fixture: a research map with every cell grounded and nothing else measured. LEARN steps have +/// their source link and are VERIFIED; PRACTICE and DELIVER ask for a test or a review and stay DRAFT. +pub const FIX_STATUS : [27]u8 = [4, 4, 4, 3, 3, 3, 3, 3, 3, 4, 4, 4, 3, 3, 3, 3, 3, 3, 4, 4, 4, 3, 3, 3, 3, 3, 3]; +pub const ALL : [27]bool = [true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true]; +pub const NONE : [27]bool = [false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false]; +pub const FIX_HEAD : [5]str = ["Title", "Lens", "Focus", "Evidence", "Next"]; +pub const FIX_ROWS : [20]str = ["", "", "", "", "", "", "", "", "a row", "", "", "", "a row", "a row", "", "", "a row", "", "", ""]; +pub const FIX_LONG : [20]str = ["", "", "", "", "", "", "", "", "a row far too long to sit on one line of the card on any preset at all", "", "", "", "", "", "", "", "", "", "", ""]; +pub const FIX_STAGE_ROW : [20]str = ["a row on the title strip", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ""]; + +test counts_are_counts { + assert count_true(ALL) == 27; + assert count_true(NONE) == 0; + assert count_status(FIX_STATUS, STATUS_VERIFIED) == 9; + assert count_status(FIX_STATUS, STATUS_DRAFT) == 18; + assert count_status(FIX_STATUS, STATUS_GAP) == 0; +} + +test a_drawn_status_must_be_the_earned_one { + assert statuses_earned(FIX_STATUS, DOMAIN_RESEARCH, ALL, NONE, NONE, NONE); + // one cell drawn better than its measurements: caught, and at that cell + var wrong : [27]u8 = FIX_STATUS; + wrong[3] = STATUS_VERIFIED; + assert statuses_earned(wrong, DOMAIN_RESEARCH, ALL, NONE, NONE, NONE) == false; + assert first_unearned(wrong, DOMAIN_RESEARCH, ALL, NONE, NONE, NONE) == 3; + // the same drawing is right once a test passes for every cell: PRACTICE turns VERIFIED too + assert first_unearned(FIX_STATUS, DOMAIN_RESEARCH, ALL, ALL, NONE, NONE) == 3; + // no source anywhere: every cell is a gap, so the fixture is wrong from cell 0 + assert first_unearned(FIX_STATUS, DOMAIN_RESEARCH, NONE, NONE, NONE, NONE) == 0; +} + +test a_gap_names_what_is_missing { + var sources : [27]str = ["s", "s", "s", "s", "s", "s", "s", "s", "s", "s", "s", "s", "s", "s", "s", "s", "s", "s", "s", "s", "s", "s", "s", "s", "s", "s", "s"]; + var missing : [27]str = ["", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ""]; + assert gaps_are_named(sources, missing, ALL); + var grounded : [27]bool = ALL; + // a gap with nothing said about it is refused + sources[5] = ""; + grounded[5] = false; + assert gaps_are_named(sources, missing, grounded) == false; + missing[5] = "the run that would ground it"; + assert gaps_are_named(sources, missing, grounded); + // a grounded cell may not also claim to be missing something, nor a sourceless one be grounded + missing[6] = "x"; + assert gaps_are_named(sources, missing, grounded) == false; + missing[6] = ""; + assert gaps_are_named(sources, missing, ALL) == false; +} + +test slide_text_fits_or_is_refused { + assert row_count(FIX_ROWS, 2) == 1; + assert row_count(FIX_ROWS, 3) == 2; + assert slides_fit(FIX_HEAD, FIX_ROWS); + assert slides_fit(FIX_HEAD, FIX_LONG) == false; + var long_head : [5]str = FIX_HEAD; + long_head[0] = "A headline much too long to stay on one line of the title strip"; + assert slides_fit(long_head, FIX_ROWS) == false; + var no_head : [5]str = FIX_HEAD; + no_head[4] = ""; + assert slides_fit(no_head, FIX_ROWS) == false; +} + +test stage_slides_carry_no_card_text { + // why a headline is one line: on the wide frame a second stage line would not fit the strip + assert text_fits(2, 0, LAYOUT_STAGE, 0) == false; + assert stage_slides_bare(FIX_ROWS); + assert stage_slides_bare(FIX_STAGE_ROW) == false; +}