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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude/skills/t27f/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ the reels. Describing T27F as #1677 is the error this section exists to stop (ow
| Renderer in the Trinity UI | gHashTag/trinity | **missing**; no T27F widget is live |
| Application: topic map (#1677) | `specs/t27f/topic_engine.t27`, `topic_reel.t27` | spec; procedure below |
| Application: reels and deck | `specs/t27f/reel.t27`, `deck.t27`, `reel_*.t27` | spec; two reels |
| Application: content factory (epic t27#10560) | `specs/t27f/reel_factory.t27` (t27#10561) | spec with tests: an item's code and title, read keyword by keyword, give 11 features. Those pick 1 of 12 scene archetypes (best pair first, cap 4, no repeat within 2 items, then the least-used generic scene) plus an accent, a motion, a layout and 8 output formats. No renderer draws a plan yet (R3), and `lesson_reel` still draws token_floor (R4). Lessons: read the plan's spread over the whole course before trusting it. A greedy walk in course order gave 7 lessons one scene. Code counts alone handed lesson 8 the tests scene over "A test block". A printing `main` makes t27b reject the spec |
| Application: content factory (epic t27#10560) | `specs/t27f/reel_factory.t27` (t27#10561) | spec with tests: an item's code and title, read keyword by keyword, give 11 features. Those pick 1 of 12 scene archetypes (best pair first, cap 4, no repeat within 2 items, then the least-used generic scene) plus an accent, a motion, a layout and 8 output formats. No renderer draws a plan yet (R3), and `lesson_reel` still draws token_floor (R4). Lessons: read the plan's spread over the whole course before trusting it. A greedy walk in course order gave 7 lessons one scene. Code counts alone handed lesson 8 the tests scene over "A test block". Gate: `tri factory`. The plan prints from a separate module, `specs/t27f/reel_factory_plan.t27` (t27#10573): `t27c gen` it, then run the zig (t27c has no `run`). Names come from the factory's comma lists, so the law module keeps no `main`. Traps: the lab's `/root/t27` t27b can predate #10455 and refuse `std.debug.print` that master accepts, so build a fresh t27b in its own worktree with its own `CARGO_TARGET_DIR` before you call a print unsupported. `t27c seal --save` needs zig on PATH (`/opt/zig`). A base64 line fetched back from fs-lab merges with the Railway warning, so wrap it as `B64:...:END`. A new `tri` arm must start its own line (` name)`) or the documented-commands checker misses it |
| Related | trinity#1647 dashboards, #1652 MVP research, #1653 three skills, #1655 evolution runtime | open |

When asked "how is T27F going", report against the core rows first. Progress on the applications
Expand Down
37 changes: 37 additions & 0 deletions .trinity/seals/t27f_reel_factory_plan.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"built_by": "t27c-bootstrap@0.5.2+9448ab4be",
"closure": [
{
"spec": "specs/t27f/reel_factory.t27",
"spec_hash": "sha256:96099d764ba86fa3ef2c29d81c3838ce255bfd3b3abbd37b27cb86e4bca91012"
},
{
"spec": "specs/t27f/reel_topics.t27",
"spec_hash": "sha256:7c1762cde96524791711594d23f880fbad1f497a499277710f1fd61d52d50157"
}
],
"config": "gen=c,rust,verilog,zig;tests=zig",
"gen_hash_c": "sha256:861641e09545b66dd73e9e63988853f140d9b6bf4ff1c8f87461dfdffd859b5f",
"gen_hash_rust": "sha256:0df5cdc9d474435a712a6628be2b9e4004f21cbfaec003a9740a09637cbdcb58",
"gen_hash_verilog": "sha256:cc61c0aa8776412954be480c0e2284d32178b5db0a3579d3b4e2d3c847f132ef",
"gen_hash_zig": "sha256:1cc73810b1c24913cdfd6589f4d01f0c5c205b6d148049c395f4a6a700883b6c",
"module": "reel_factory_plan",
"ring": 12,
"seal_schema": 2,
"sealed_at": "2026-10-18T17:45:41Z",
"sealed_by": "t27c-bootstrap@0.5.2",
"spec_hash": "sha256:2d2373991269e2f9d252c07a274484dd983c9eda6e543cc4338620d77a71604c",
"spec_path": "specs/t27f/reel_factory_plan.t27",
"tests": {
"failed": 0,
"failing": [],
"forced": false,
"passed": 4,
"total": 4,
"vacuous": 0
},
"toolchain": {
"test_runner": "sha256:8512b80da533b79289110fca4a65f3336abd7a059ca4b72ea595d5e33ff8ad10",
"zig": "0.16.0"
}
}
1 change: 1 addition & 0 deletions docs/reports/t27b_expectations.json
Original file line number Diff line number Diff line change
Expand Up @@ -1448,6 +1448,7 @@
{"path": "specs/t27f/quest.t27", "t27b": "pass"},
{"path": "specs/t27f/reel.t27", "t27b": "pass"},
{"path": "specs/t27f/reel_factory.t27", "t27b": "pass"},
{"path": "specs/t27f/reel_factory_plan.t27", "t27b": "pass"},
{"path": "specs/t27f/reel_gf16_nmse.t27", "t27b": "pass"},
{"path": "specs/t27f/reel_marketing.t27", "t27b": "pass"},
{"path": "specs/t27f/reel_particles.t27", "t27b": "blocked", "blocker": "ExprCall(@sin)", "reason": "unimplemented"},
Expand Down
10 changes: 5 additions & 5 deletions scripts/tri
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ tri -- the t27 front door.

Compiler commands (forwarded to t27c):
tri test [--json] [...] run the conformance suite (t27c suite --repo-root .)
tri papers recompute the numbers three format papers print (specs/numeric/paper_claims.t27)
tri papers | factory the numbers three format papers print (specs/numeric/paper_claims.t27) | the reel factory's uniqueness gate (specs/t27f/reel_factory.t27)
tri <anything else> forwarded to t27c unchanged (gen, seal, fpga-build, ...)

Local commands (no compiler needed):
Expand Down Expand Up @@ -531,7 +531,8 @@ if [[ ! -x "$T27C" ]]; then
exit 2
fi

[[ "$cmd" == "papers" ]] && exec "$T27C" test-report "$REPO_ROOT/specs/numeric/paper_claims.t27" "${@:2}"
case "$cmd" in papers) exec "$T27C" test-report "$REPO_ROOT/specs/numeric/paper_claims.t27" "${@:2}";;
factory) exec "$T27C" test-report "$REPO_ROOT/specs/t27f/reel_factory.t27" "${@:2}";; esac
# Most subcommands are implemented in the Rust `tri` binary (cli/tri),
# which is a DIFFERENT binary from t27c. Everything not handled above was
# forwarded to t27c, so every one of them existed and was unreachable through the
Expand All @@ -543,9 +544,8 @@ fi
# as the fix when the gate fails, which answered `unrecognized subcommand
# 'now'`. A gate whose remediation does not run is a gate that fails twice.
#
# Only commands t27c does NOT implement are rerouted, so no existing route
# moves: ci, gates, gen, health, serve, test and verdict are names both
# binaries carry, and they keep going exactly where they went before.
# Only commands t27c does NOT implement are rerouted, so no existing route moves: ci, gates, gen,
# health, serve, test and verdict are names both binaries carry, and they keep going where they went.
if [[ "$cmd" =~ ^[a-z][a-z0-9-]*$ ]]; then
TRI_BIN="${TRI_BIN:-}"
if [[ -z "$TRI_BIN" ]]; then
Expand Down
199 changes: 199 additions & 0 deletions specs/t27f/reel_factory_plan.t27
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
// SPDX-License-Identifier: Apache-2.0
// specs/t27f/reel_factory_plan.t27 -- prints the content factory's plan for course 0, one
// tab-separated row per lesson. Issue gHashTag/t27#10573, epic gHashTag/t27#10560.
//
// WHY: reel_factory.t27 decides every lesson's scene, but the plan lived only inside its tests.
// Its first version printed from a main, and t27b refused that main on std.debug.print, so the
// main was dropped. t27b now lowers print with a literal format of {d} and {s} (#10455), so the
// printer lives here, apart from the law it reads. reel_factory.t27 stays a module of decisions
// and nothing else, and this module only reads it out.
//
// HOW (main): t27c gen specs/t27f/reel_factory_plan.t27 > plan.zig, then run plan.zig with zig.
// It prints a header, 27 rows of
// lesson scene accent motion layout slides title
// and one summary line: how many of the twelve scenes are used, and the most uses of any one.
// Every name is read out of the factory's own comma lists (ARCH_NAMES, ACCENT_NAMES,
// MOTION_NAMES, LAYOUT_NAMES), so a renamed scene cannot drift between the law and its printout.
//
// NOT CLAIMED:
// - A title byte outside printable ASCII prints as '?'. The English titles of course 0 are ASCII
// today, and the tests check that, but the printer does not encode UTF-8.
// - The printout is a reading of arche(), not a second decision. Each row calls arche(t), which
// plans the whole course again. That is 27 plans, cheap at 27 lessons, and a cost to revisit
// if a course grows to hundreds of items.

module reel_factory_plan;

use t27f::reel_topics::TOPICS;
use t27f::reel_topics::item;
use t27f::reel_topics::item_len;
use t27f::reel_topics::cp;
use t27f::reel_factory::ARCHS;
use t27f::reel_factory::ARCH_CAP;
use t27f::reel_factory::ARCH_NAMES;
use t27f::reel_factory::ACCENTS;
use t27f::reel_factory::ACCENT_NAMES;
use t27f::reel_factory::MOTIONS;
use t27f::reel_factory::MOTION_NAMES;
use t27f::reel_factory::LAYOUTS;
use t27f::reel_factory::LAYOUT_NAMES;
use t27f::reel_factory::A_TRIT;
use t27f::reel_factory::arche;
use t27f::reel_factory::accent;
use t27f::reel_factory::motion;
use t27f::reel_factory::layout;
use t27f::reel_factory::carousel_slides;

pub const TITLE_MAX : usize = 48;
pub const COMMA : u8 = 44;
pub const UNPRINTABLE : u8 = 63;

// ---- names ----

// Where field k of a comma-separated list begins; names.len when the list has k fields or fewer.
pub fn field_start(names: []const u8, k: usize) -> usize {
var p : usize = 0;
var seen : usize = 0;
while (p < names.len && seen < k) {
if (names[p] == COMMA) {
seen = seen + 1;
}
p = p + 1;
}
return p;
}

// One past the last byte of field k.
pub fn field_end(names: []const u8, k: usize) -> usize {
var p = field_start(names, k);
while (p < names.len && names[p] != COMMA) {
p = p + 1;
}
return p;
}

// How many fields a comma-separated list has.
pub fn fields(names: []const u8) -> usize {
var n : usize = 1;
var p : usize = 0;
while (p < names.len) {
if (names[p] == COMMA) {
n = n + 1;
}
p = p + 1;
}
return n;
}

// ---- titles ----

// How many bytes of lesson t's English title the printout shows.
pub fn title_n(t: usize) -> usize {
const n = item_len(item(t, 0, 0));
if (n > TITLE_MAX) {
return TITLE_MAX;
}
return n;
}

// Byte k of lesson t's English title, or UNPRINTABLE for a code point outside printable ASCII.
pub fn title_byte(t: usize, k: usize) -> u8 {
const c = cp(item(t, 0, 0), k);
if (c < 32 || c > 126) {
return UNPRINTABLE;
}
return @as(u8, @intCast(c));
}

// ---- main ----

pub fn main() -> void {
var used : [12]usize = undefined;
var a : usize = 0;
while (a < ARCHS) {
used[a] = 0;
a = a + 1;
}
std.debug.print("lesson\tscene\taccent\tmotion\tlayout\tslides\ttitle\n", .{});
var t : usize = 0;
while (t < TOPICS) {
const s = arche(t);
used[s] = used[s] + 1;
const c = accent(t);
const m = motion(t);
const y = layout(t);
var buf : [48]u8 = undefined;
const n = title_n(t);
var k : usize = 0;
while (k < n) {
buf[k] = title_byte(t, k);
k = k + 1;
}
std.debug.print("{d}\t{s}\t{s}\t{s}\t{s}\t{d}\t{s}\n", .{ t, ARCH_NAMES[field_start(ARCH_NAMES, s)..field_end(ARCH_NAMES, s)], ACCENT_NAMES[field_start(ACCENT_NAMES, c)..field_end(ACCENT_NAMES, c)], MOTION_NAMES[field_start(MOTION_NAMES, m)..field_end(MOTION_NAMES, m)], LAYOUT_NAMES[field_start(LAYOUT_NAMES, y)..field_end(LAYOUT_NAMES, y)], carousel_slides(t), buf[0..n] });
t = t + 1;
}
var kinds : usize = 0;
var most : usize = 0;
a = 0;
while (a < ARCHS) {
if (used[a] > 0) {
kinds = kinds + 1;
}
if (used[a] > most) {
most = used[a];
}
a = a + 1;
}
std.debug.print("{d} of {d} scenes used, the most used one {d} times (cap {d})\n", .{ kinds, ARCHS, most, ARCH_CAP });
}

// ---- tests ----

test "a comma list reads field by field, first, middle and last" {
assert(field_start(ARCH_NAMES, 0) == 0);
assert(field_end(ARCH_NAMES, 0) == 4);
assert(ARCH_NAMES[field_start(ARCH_NAMES, 3)] == 116);
assert(field_end(ARCH_NAMES, 3) - field_start(ARCH_NAMES, 3) == 4);
assert(field_end(ARCH_NAMES, ARCHS - 1) == ARCH_NAMES.len);
assert(field_end(ARCH_NAMES, ARCHS - 1) - field_start(ARCH_NAMES, ARCHS - 1) == 3);
assert(field_start(ARCH_NAMES, ARCHS) == ARCH_NAMES.len);
assert(ACCENT_NAMES[field_start(ACCENT_NAMES, ACCENTS - 1)] == 119);
}

test "every list has exactly as many names as the factory has kinds" {
assert(fields(ARCH_NAMES) == ARCHS);
assert(fields(ACCENT_NAMES) == ACCENTS);
assert(fields(MOTION_NAMES) == MOTIONS);
assert(fields(LAYOUT_NAMES) == LAYOUTS);
var k : usize = 0;
while (k < ARCHS) {
assert(field_end(ARCH_NAMES, k) > field_start(ARCH_NAMES, k));
k = k + 1;
}
}

test "every lesson's title prints, ASCII and within the width" {
var t : usize = 0;
while (t < TOPICS) {
const n = title_n(t);
assert(n > 0);
assert(n <= TITLE_MAX);
var k : usize = 0;
while (k < n) {
assert(title_byte(t, k) != UNPRINTABLE);
k = k + 1;
}
t = t + 1;
}
}

test "the printed scene of the trits lesson is the word trit" {
const s = arche(7);
assert(s == A_TRIT);
const b = field_start(ARCH_NAMES, s);
assert(field_end(ARCH_NAMES, s) - b == 4);
assert(ARCH_NAMES[b] == 116);
assert(ARCH_NAMES[b + 1] == 114);
assert(ARCH_NAMES[b + 2] == 105);
assert(ARCH_NAMES[b + 3] == 116);
}
Loading