Skip to content

art: make the deck the unit, so nine frames read as one deck - #319

Merged
Talonsturgill merged 12 commits into
mainfrom
tsturg/pensive-mayer-h3ekki
Sep 17, 2026
Merged

Talonsturgill merged 12 commits into
mainfrom
tsturg/pensive-mayer-h3ekki

Conversation

@Talonsturgill

Copy link
Copy Markdown
Owner

Owner's report, 2026-09-16: the artwork is not good enough, the slides do not flow together, and each frame reads as text boxes thrown on a page.

What was actually wrong, measured rather than argued

Four facts read off shipped artifacts in this product and the sibling:

this product sibling
slides loading the film grade (txpost.js) 1 of 205 127 of 127
slides loading the OKLCH ramp builder (txcolor.js) 1 of 205 118 of 127
per-run deck chassis ever written 0 in 26 decks one per run
median adjacent value jump across decks 21.0 L* 2.6 L*
decks carrying at most one hard cut (>25 L*) 0 of 19 52 of 58 (90%)

The grade and the ramp builder were ported, working and documented, and essentially never called. That alone is most of the flat look.

The deeper cause is that there was no deck. Every frame reached into a permanent bin of finished parts (txobjects, txfig), which gives uniform PARTS and no unity of WORLD. That is why txobjects.js, written in September specifically to cure "clip art", did not cure it. The sibling writes a new chassis per run, named for that deck's world, and all nine frames draw from it.

And the machine was being told to be incoherent. txlayout.js required nine different layouts, at least five distinct, no archetype twice running, and a print screen that varies per frame. The rubric's top artwork score required "no other frame in the deck laid out the same way". The 2026-09-16 craft judge praised frame 6 for being "the identical camera to frame 3 with the light inverted" and in the same breath gave the deck credit for "nine distinct layouts". The rule was pulling against the thing the judge liked.

The scorer was also reading the value track backwards, calling a sawtooth "a genuine value arc" and rewarding its amplitude, because nothing measured adjacency. A spread is a property of a set. A deck is a sequence.

What changed

assets/js/txdeck.js holds the three invariants a deck cannot keep by good intentions: one light, one material ramp, one way of seating type, plus finish(), the one line that grades a frame with the deck's own grade. The chassis declares the deck exactly once, so nine frames cannot hold nine lights. Coherence is structural rather than checked.

ownership.yaml carves assets/js/deck/** to the daily lane while assets/** stays human. A run may build a world. It may not edit the workshop. Without this the pre-commit hook would have refused every chassis and the upgrade would have failed silently in production.

Two new gates. deck_chassis.py reads the source and refuses a deck whose frames don't share a chassis, skip the grade, grade themselves, redeclare the light, or put a plate behind type; it finds 33 defects in the shipped 2026-09-16 deck. deck_coherence.py reads the pixels and refuses a deck that strobes. Its thresholds are derived from a 58-deck reference corpus and recorded with their full derivation in config/carousel/deck_coherence.json, never typed.

The doctrine inverts. ILLUSTRATION_SYSTEM.md gains "THE DECK IS THE UNIT", which outranks every per-frame rule under it: the chassis law, one light and ramp and screen and grade for the whole deck, a continuity mandate of at least two devices named in the storyboard, and no plate ever. The rotation is rebalanced rather than deleted: at most two of the same archetype in a row (two is a beat, three is a rut), at least three distinct. The image law did not move, because the defect the old table was written for was never "too few layouts", it was "no image".

The rubric splits the art question. artwork_craft 0.28 → 0.22 asks only about a frame and no longer asks for a different layout on every one. A new deck_coherence at 0.12 asks whether the nine are one deck. Art carries 0.34 between them, up from 0.28, and claim_integrity did not move. The threshold goes 6.7 → 8.0: the last fourteen runs scored 6.56 to 7.58, so the bar sat below the mean of the thing it judged and never once asked for another round.

layout_check --prose refuses any surface still stating the superseded rule, and separates paths this repo can write (FAIL) from the two critic definitions under .claude/ that the host lets nobody write unattended (reported, with the backlog item that closes them). Phase 12 now hands the flow critic the current rule at spawn time until a maintainer makes that edit.

Proof

examples/lamp-deck/ is the 2026-09-16 story rebuilt in the new system, same claims, same copy, so the contact sheets are a clean A/B on artwork alone.

shipped 2026-09-16 rebuilt
mean adjacent value jump 23.83 L* 5.39 L* (sibling median 2.6, its p75 6.45)
value spread 59.9 19.1
hard cuts 4 0
opaque plates behind type 6 of 9 frames 0
frames loading the grade 0 of 9 9 of 9
machine QA failures 0 0, via 34 at the first build

One world, one lamp at az 64 el 26, one ramp, one screen, one grade. The camera moves through that world rather than the world changing per frame. Two continuity devices run: the camera returns to frame 1's exact position on frame 9, and the hint tick evolves one, two, two, three, then absent, so the close argues the claim by not drawing it.

The eight things that build cost are written into ILLUSTRATION_SYSTEM.md, among them that a hole punched in a light layer is a plate with the sign flipped, that a frame's two kinds of type want opposite things from the light, that a mid ground is the worst ground and no ink fixes it, that canvas text can't be registered to DOM text in a variable font, and that a frame whose QA goes sideways across rounds wants recomposing rather than tuning.

Raising the bar broke three gates, and all three were right

run_complete pins the threshold in its own self-test so it cannot drift with nobody able to name the run that moved it. The pin did its job: the rubric edit alone went red and the bar could not move without a second deliberate commit. panel's ship-false case used judges at 7.5, which cleared the old bar and not the new one. And shipped_check marked three already-published decks as never having shipped, at 7.42, 7.42 and 7.09, all of which cleared the 7.0 in force when they shipped; BAR_HISTORY now gives a deck that recorded no threshold the bar that actually applied.

Known, and not this PR's

scripts/shared/port_audit.py fails its parity check on three missing config/brand.yaml keys. This is red on main too and is unrelated to artwork; verified by running the audit against a clean origin/main worktree. Fixing it means real brand-config decisions, so it is reported rather than folded in here.

The two critic definitions under .claude/agents/ still carry the superseded rotation rule. No routine may write that path, so knowledge/carousel/UPGRADE_BACKLOG.md carries the four-line fix for a maintainer, and the routine hands the critics the current rule at spawn time in the meantime.

🤖 Generated with Claude Code

https://claude.ai/code/session_01QGXgJMCsDkwKBDTKYgWdU6


Generated by Claude Code

…re a deck

The owner reported on 2026-09-16 that the carousel artwork does not flow together and
that each frame reads as text boxes thrown on a page. Four measurements under that:

  - txpost.js, the film grade, was loaded by 1 shipped slide of 205. The sibling
    product loads its own copy on 127 of 127. txcolor.js ran 1 of 205 against 118
    of 127. The finishing pass was in this repo the whole time and was never called.
  - No deck ever had a chassis. Every frame reached into a permanent bin of finished
    parts, which gives uniform parts and no unity of world. The sibling writes one
    chassis per run, named for that deck's world, and all nine frames draw from it.
  - Six frames of nine put an opaque plate behind the headline.
  - Median adjacent value jump across nineteen shipped decks was 21.0 L* against the
    sibling's 2.6. Ninety percent of sibling decks carry at most one hard cut. Not
    one deck here did.

assets/js/txdeck.js holds the three invariants a deck can't keep by good intentions:
one light, one material ramp, one way of seating type, plus finish(), the one line
that grades a frame with the deck's own grade.

scripts/carousel/deck_chassis.py reads the source and refuses a deck whose frames
don't share a chassis, skip the grade, grade themselves, redeclare the light, or put
a plate behind type. It finds 33 defects in the 2026-09-16 deck.

scripts/carousel/deck_coherence.py reads the pixels and refuses a deck that strobes.
Its thresholds are derived from a 58 deck reference corpus and recorded with their
derivation in config/carousel/deck_coherence.json, never typed.

ownership.yaml carves assets/js/deck/** to the daily lane so a run can build a world
while assets/** stays human, so a run still can't edit the workshop.
The rotation rule was mandating the defect. It required nine different layouts, at
least five distinct, no archetype twice in a row, and a screen that varies with the
layout. The machine was obeying it correctly. It was told the wrong thing, and the
owner's report that the slides do not flow together is what that rule produces.

ILLUSTRATION_SYSTEM.md gains THE DECK IS THE UNIT, which outranks every per frame
rule under it: the chassis law, one light and one ramp and one grade and one screen
for the whole deck, a continuity mandate of at least two devices named in the
storyboard, and no plate ever.

The rotation is rebalanced rather than deleted. At most two of the same archetype in
a row (two is a beat, three is a rut), at least three distinct. The image law did not
move, because the defect the old table was written for was never too few layouts, it
was no image.

The rubric splits the art question in two. artwork_craft 0.28 to 0.22 asks only about
a frame and no longer asks for a different layout on every one. A new deck_coherence
at 0.12 asks whether the nine are one deck and tells a judge that a spread is a
property of a set while a deck is a sequence. Art carries 0.34 between them, up from
0.28, and claim_integrity did not move.

The threshold goes 6.7 to 8.0. The last fourteen runs scored 6.56 to 7.58, so the bar
sat below the mean of the thing it judged and never once asked for another round. The
sibling holds 8.3 on the same agents and engine. max_rounds still bounds the search,
so a run can still ship, honestly, under the bar.

layout_check gains --prose, which refuses any surface still stating the superseded
rule. It finds two critic definitions under .claude/ that no routine may write, the
flow critic among them, so Phase 12 now hands the flow critic the current rule at
spawn time and UPGRADE_BACKLOG.md carries the four line fix for a maintainer.
examples/lamp-deck/ is the 2026-09-16 story rebuilt in the new system, same claims
and same copy, so the two contact sheets are a clean A/B on artwork alone.

Measured, old deck to new:

  mean adjacent value jump   23.83 -> 5.39 L*   (sibling median 2.6, its p75 6.45)
  value spread               59.9  -> 19.1
  hard cuts (>25 L*)         4     -> 0
  opaque plates behind type  6 of 9 frames -> 0
  frames loading the grade   0 of 9 -> 9 of 9
  machine QA failures        0 -> 0, via 34 at the first build

The deck now holds one world, one lamp at az 64 el 26, one ramp, one screen and one
grade across nine frames, and the camera moves through that world rather than the
world changing per frame. Two continuity devices run: the camera returns to frame 1's
exact position on frame 9, and the hint tick evolves one, two, two, three and then
absent, so the close argues the claim by not drawing it.

ILLUSTRATION_SYSTEM.md gains the eight things this build paid for, among them that a
hole punched in a light layer is a plate with the sign flipped, that a frame's two
kinds of type want opposite things from the light, that a mid ground is the worst
ground and no ink fixes it, that canvas text can't be registered to DOM text in a
variable font, and that a frame whose QA goes sideways across rounds wants
recomposing rather than tuning.
…achability

guards.yml gains five steps: both new gates' self-tests, the reference chassis deck
against deck_chassis, its bespoke reading recorded rather than enforced, and the
prose agreement check.

The bespoke step is deliberately non-blocking and says why in the file. A chassis
deck scores HIGHER on pairwise similarity than a deck of nine unrelated drawings,
because nine frames sharing one world share a drawing vocabulary. The sibling's own
chassis decks measure 0.36 and 0.45 with closest pairs at 0.93 and 0.76, this one
sits inside that range, and the old incoherent decks scored LOWER at 0.30. That step
exists so a future run reading the WARN does not close it by dismantling the chassis.

The prose gate separates the surfaces this repo can write from the two agent
definitions under .claude/, which ownership.yaml gives to upgrade and which the host
lets nobody write unattended. Reachable surfaces FAIL. The blocked ones are reported
loudly with the backlog item that closes them. Failing CI on a path nothing in this
repo can fix would stop every run for a defect no run can close, which is the shape
of the mistake CLAUDE.md spent six wedged days learning.
Raising the threshold to 8.0 turned three local steps red, and all three were right
to go red.

run_complete pins the bar in its own self-test on purpose, so it cannot drift a tenth
at a time with nobody able to name the run that moved it. The pin did exactly its job:
the rubric edit alone went red and the bar could not move without a second deliberate
commit naming it. Pin updated, with the move recorded beside the 2026-09-13 one.

panel's ship:false case used judges at 7.5, which cleared the old bar and does not
clear the new one, so the case stopped being the one under test and became an ordinary
threshold dissent. Scores moved to 8.6 and the comment says why, because the next
person to move the bar will hit this again.

shipped_check ran the current bar against every deck ever published and marked
2026-08-20, 2026-08-21 and 2026-08-22 as never having shipped, at 7.42, 7.42 and 7.09.
All three cleared the 7.0 in force when they shipped. None of them changed.

That file already settled this principle on 2026-09-13 and implemented half of it: a
deck that RECORDED its threshold answers to that one. The decks that recorded none
fell through to 'the current one', which was harmless while the bar only ever went
down. This is the first time it went up. BAR_HISTORY gives those decks the bar that
actually applied, read from the rubric's own git history and from run_complete's
docstring for the pre-rubric 7.0, and anything after the last entry still answers to
the rubric as it stands.
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 17, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-17T01:42:30.550483Z 0349735 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

tests/txlayout.mjs asserted that two of the same archetype in a row FAILS, which is
the rule that forbade the deck its strongest continuity move and which changed with
the rotation rebalance. The Python copy and its self-test were retargeted with the
rule and this one was missed, which is the third copy of the same numbers in this
repo and exactly what layout_check --prose now exists to catch for prose surfaces.

Two in a row is a beat, the same camera with the light moved. Three is a rut, and
there is now a case asserting that.

Copy link
Copy Markdown
Owner Author

browser-render is red, and it is not this PR's

The failing check: browser-render, on tests/page_ground.mjs:

FAIL  the horizon is warmer than the sky above it   top r-b -7, horizon r-b -14
page_ground: 1 FAILED

Why it is not this PR's. main is red on the same job, and has been for at least its last six guards runs (1211 through 1216, back to 2026-09-16 17:46). Run 1216 on main (f4f1a95) has exactly one failed job and it is browser-render. This branch touches neither docs/ nor scripts/site/, so it cannot have caused a check that samples pixels off the published site.

No fix exists to port. The check measures the hero's atmosphere: it wants the horizon warmer than the sky above it, and both sample points are currently cool (-7 at the top, -14 at the horizon), with the horizon the colder of the two. The gate's own comment says why it is written that way, so that the cure for a mauve page cannot be "delete the atmosphere". Correcting it means changing the published site's hero colours, which is a design decision on a surface this PR does not touch, so porting a guess here would widen an artwork PR into the site's visual identity. Flagged rather than folded in.

Two more, also red on main and also not this PR's

port_audit parity. Three keys missing from config/brand.yaml with no entry in config/parity_map.yaml: brand.date_format.drop_the_year_when, visual.constellation.site_placement, visual.constellation.source_note. Verified identical against a clean origin/main worktree. Writing those entries is a real brand decision, and parity_map.yaml says so in its own header, so it is not one to make in passing.

site_fresh_check, and this one looks worse than a stale build. It reports docs/ disagreeing with the ledgers. Running the sanctioned cure, scripts/site/site_build.py, produces 324 deletions, including the article pages for the 2026-09-14, 2026-09-15 and 2026-09-16 decks that are currently published. The rebuild's article grid starts at 2026-09-13. That was reverted rather than pushed: the builder currently emits a site with three recent decks missing, and committing it would have removed live pages. Worth its own session before the next routine run, whose Phase 16 rebuilds docs/ and would commit exactly that.

What this PR's own gates say

deck_chassis, deck_coherence, layout_check (including --prose), bespoke_check, panel, run_complete, shipped_check and tests/txlayout.mjs all pass locally, and the reference deck in examples/lamp-deck/ renders with 0 machine-QA failures.


Generated by Claude Code

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0349735563

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +564 to +565
- name: Deck coherence self-test (a spread is a set, a deck is a sequence)
run: python3 scripts/carousel/deck_coherence.py --self-test

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Run the new gates against the shipped deck

This workflow only self-tests deck_coherence.py; the chassis check exercises the fixed reference example, and neither module is registered in shipped_check.py. Consequently, if a daily run skips or misapplies the prompt's local commands, CI can still merge a newly shipped deck that strobes or lacks a valid chassis while the workflow reports green. Invoke both gates against the newest committed run, as the existing shipped-deck sweep does for other product gates.

AGENTS.md reference: AGENTS.md:L68-L69

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct, and this is gate_wiring.py's own finding in a new place: a gate nothing points at its real subject. Self-testing a checker proves it can go red; running it against a fixed example proves it agrees with a deck that cannot change. Neither asks it about the deck a run just shipped.

Fixed in 118cb84. g_deck_chassis and g_deck_coherence are registered in shipped_check.py's table as CURRENT, so the sweep that already runs every other product gate across all 26 published runs now runs these two as well.

Both carry DECK_SINCE = "2026-09-16", for the reason CONSTRUCTION_SINCE and LAYOUT_SINCE already carry theirs: no deck on or before today has a chassis because the system did not exist, and every one of them strobes by thresholds derived today. A gate does not judge the work that produced it. The sweep reports both as not-applicable with that reason printed, so the exemption is visible rather than silent, and it expires on its own with the next deck.


Generated by Claude Code

Comment thread scripts/carousel/deck_chassis.py Outdated
Comment on lines +145 to +148
if "TXDECK.finish(" not in html:
problems.append(
f"{name}: never calls TXDECK.finish, so it ships ungraded. This is the call that "
f"was missing from 204 of 205 shipped slides")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Verify that the finishing pass actually runs last

This substring test passes when TXDECK.finish( appears only in a comment, or when a slide calls it and then performs additional canvas drawing. In either case the gate reports success even though some or all artwork bypasses the deck grade, directly violating the new last-canvas-operation contract. Parse executable calls or otherwise verify that no canvas mutation follows the finish call.

AGENTS.md reference: AGENTS.md:L68-L69

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right on both halves, and the second one is the worse of the two: a frame that grades and then keeps drawing reports itself graded while shipping artwork the grade never saw.

Fixed in 118cb84. finish_problems() now strips block comments, finds TXDECK.finish( calls that are not commented out, and refuses any canvas mutation after the last one (fillRect, drawImage, putImageData, beginPath, the gradient constructors and the rest). A mention that exists only in a comment gets its own message, because "you commented it out" and "you never wrote it" are different mistakes.

Three cases added: a line-commented call, a block-commented call, and TXDECK.finish(...); cx.fillRect(...). All three were passing before.


Generated by Claude Code

Comment thread ownership.yaml
Comment on lines +265 to +266
- path: "assets/js/deck/**"
owner: daily

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Prevent later runs from overwriting a deck chassis

The filename is derived only from a free-form world name, while this rule lets every daily run modify any existing chassis. If a later deck reuses a name such as lamp, it can overwrite assets/js/deck/lamp.js; archived slide HTML under runs/carousel/<date>/slides still references that shared path, so reproducing or checking the earlier run now executes the later deck's chassis. Make chassis paths run-unique or enforce create-only behavior so shipped artifacts retain their original dependency.

AGENTS.md reference: AGENTS.md:L37-L37

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the most consequential of the eight and it was a real latent bug, not a style point. shipped_check.py re-runs the gates against every deck this project has published, so a collision would not have waited for somebody to reproduce an old run by hand — the next sweep would have re-checked an earlier deck against a later deck's light, ramp and grade.

Fixed in 118cb84 by making the path unique rather than by a create-only rule, because create-only is a rule somebody has to remember and a filename is not. Chassis files are assets/js/deck/<date>-<world>.js; CHASSIS_NAME_RE refuses anything else and says why; the reference chassis moved to 2026-09-16-lamp.js with its nine slides and the builder updated, and it re-renders clean. ownership.yaml's note carries the reasoning where the carve-out lives.

A self-test case now loads deck/caprock.js and is refused on the name.


Generated by Claude Code

Comment thread prompts/daily_routine.md
Comment on lines +956 to +961
**TELL THE FLOW CRITIC THE ROTATION RULE CHANGED, IN THE SPAWN PROMPT, EVERY ROUND.** Its own
definition under `.claude/agents/` still says "no two frames in a row laid out the same way, at
least five layouts across nine" and "the print register varies with the layout". Those are the
SUPERSEDED rule as of 2026-09-16, no routine may edit a file under `.claude/`, and a critic
enforcing a superseded rule argues the deck back toward the defect it was changed to fix. So
hand it the current rule with the deck:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Override the treatment directors' stale rotation rule

The workaround updates only the flow critic, but layout_check.py --prose also identifies carousel-treatment-director.md as stale: it still requires at least five layouts and a screen that varies per frame. Those directors run earlier in Phase 9 and seed the dossiers, while the new Node check merely permits three layouts and therefore does not reject their old five-layout plans. Pass the same current-rule override when spawning treatment directors, or they will keep planning decks toward the exact incoherence this change is intended to remove.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct, and the ordering argument is the part I had missed. The directors run in Phase 9 and their pitches become the dossiers, so overriding only the flow critic left the plan aimed at the superseded rule and gave the critic something it could only complain about after the fact.

Fixed in 118cb84. Phase 9 now opens with the override, before any director is spawned, and the Phase 12 paragraph points at it so the two cannot drift. The directors are also told to name the deck's continuity devices, since layout_check --require refuses a storyboard declaring fewer than two and the directors are who decide them.

layout_check --prose reports zero findings on every surface this repo can write. The two remaining are the agent definitions themselves, which no routine may edit; that gate now separates reachable surfaces (fail) from those (reported), and UPGRADE_BACKLOG.md carries the four-line fix.


Generated by Claude Code

Comment thread assets/js/txlayout.js
// liked. A deck of nine FULL_BLEED frames with a real image on each and a spine running
// through them is excellent. A deck of nine different layouts with a headline over a small
// object is what the judges called clip art.
var ROTATION = {"max_consecutive": 2, "min_distinct": 3, "max_type_as_object": 1, "min_full_bleed_or_close_crop": 2, "min_primary_area": 0.30, "min_bleed_frames": 4, "min_continuity_devices": 2};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Enforce the continuity-device minimum

The new rotation table declares min_continuity_devices: 2, but TXLAYOUT.check() accepts only an archetype sequence and never reads this key; the Python rotation check likewise ignores it. A nine-frame storyboard naming no spine, motif, camera move, or other continuity device therefore passes both executable rotation checks despite the new doctrine explicitly requiring two. Parse and count the storyboard declarations in a real gate rather than leaving this as an unused configuration value.

AGENTS.md reference: AGENTS.md:L68-L69

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct, and this was the worst of the eight: min_continuity_devices went into the table and nothing read it, which is GATE_LESSONS' oldest shape committed inside the same change that added a gate against that shape.

Fixed in 118cb84. TXLAYOUT.check(seq, devices) now takes the device list and a DEVICES vocabulary of the five; layout_check.declared_devices() reads CONTINUITY: <DEVICE>, <DEVICE> off the storyboard and continuity_problems() binds under --require, alongside the layout keys and for the same reason (a deck that declares none has not planned its continuity). The devices argument is optional so a caller asking only about the sequence still can, and --require is what makes the mandate bind.

Five self-test cases now cover it: two devices passes, one is refused, none is refused and told where to write them, a name off the list is named as such, and without --require it does not bind. examples/lamp-deck/storyboard.md declares CAMERA_MOVE, MOTIF_EVOLUTION and documents how each one works across the nine frames.


Generated by Claude Code

Comment on lines +122 to +123
- name: deck_coherence
weight: 0.12

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Require the panel to score every rubric criterion

Adding deck_coherence does not make it mandatory in panel.py: combine() treats any common nonzero set of criteria as complete and trusts the names and weights supplied by the judges. If all three judges return the previous six-criterion schema, the panel silently computes the old weighted result and can ship without scoring this new criterion at all; the panel self-test itself still uses that legacy schema. Validate every card's criterion names and weights against scoring_rubric.yaml before accepting the per-criterion result.

AGENTS.md reference: AGENTS.md:L68-L69

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct, including the note that the self-test itself used the legacy schema. complete meant "the judges agree with each other", and three judges all returning the same stale card agree perfectly — so the panel weighted a six-criterion rubric and reported a clean per-criterion median with deck_coherence simply absent.

Fixed in 118cb84. rubric_criteria() reads the names and weights from scoring_rubric.yaml, combine() names any declared criterion no judge scored and any criterion the judges invented, and complete now requires the set to match. Weights are checked per card rather than on the merged value, because the merged weight is whatever the last judge said and a single dissenting judge is invisible in it — my first attempt checked the merged value and the reweight case did not fire.

On the historical replays: want is injectable for the same reason bar already is. Those cases assert real numbers from the real 2026-08-19 panel, which was judged under a six-criterion rubric, so they pass their own rubric and a 2026-08-19 assertion does not come to require a criterion that did not exist then. A live run passes neither and is held to the rubric as it stands.

Four cases added. Run against the 2026-09-16 judge cards it now says no judge scored deck_coherence and falls back honestly instead of quietly weighting six.


Generated by Claude Code


version: 1
threshold: 6.7 # below this the deck does not ship
threshold: 8.0 # below this the deck does not ship

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep the email builder aligned with the raised threshold

Raising the rubric threshold to 8.0 leaves gmail_draft.py with a default threshold of 7.0, and that script's documented example omits --threshold. Any repair or manual invocation following the example will therefore describe a score such as 7.4 as "Shipped" even though the current rubric says it missed the bar; email_check.py validates the payload's shape but does not reconcile this value. Read the threshold from the rubric or make the argument mandatory instead of retaining a stale default.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct. Fixed in 118cb84: --threshold defaults to None and falls back to rubric_threshold(), which reads config/carousel/scoring_rubric.yaml and refuses to run if it declares no numeric threshold. A caller can still override it deliberately.

Same reasoning as run_complete, which this repo already states: the threshold comes from the rubric and nowhere else, because a run that could name its own bar would name a lower one on the day it mattered. The email is the owner's one look at a run, so a 7.4 captioned "Shipped" against an 8.0 bar is the worst place for a stale default to surface.


Generated by Claude Code

Comment thread scripts/carousel/deck_chassis.py Outdated
Comment on lines +78 to +82
# An opaque fill behind display type. Three ways it has been written here.
PLATE_CSS_RE = re.compile(
r"\.(?:plate|hookbox|headbox|titlebox|textbox|knockout)\b[^{}]*\{[^{}]*background", re.I)
PLATE_RGBA_RE = re.compile(
r"background\s*:\s*rgba\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*,\s*(0?\.\d+|1(?:\.0+)?)\s*\)", re.I)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Detect opaque plates independently of class names

The new no-plate gate recognizes only six selector names, while its generic fallback recognizes only rgba(...) backgrounds. A display element such as .hook { background: #08060F; }, an inline hex background, or a canvas rectangle behind the headline passes this check despite creating the exact opaque text plate the gate claims to forbid. Inspect the computed background of actual display-type elements or rendered pixels rather than relying on optional class naming.

AGENTS.md reference: AGENTS.md:L68-L69

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct. Fixed in 118cb84, and the fix taught me something worth recording.

opaque_plates() now parses every CSS rule and judges the fill's opacity on any selector that reaches display type, plus inline style= backgrounds, with _alpha_of() handling rgba(), #rrggbbaa, #rgba, bare hex and named colours. A gradient or a var() returns 0 rather than a guess, because the source cannot answer that and the render gate can.

The first cut of the fix replaced the class names with the behaviour test and lost a real catch. .plate { background: rgba(9,10,15,0.97) }, the exact defect on the 2026-09-16 deck, stopped being found, because .plate carries no display type of its own — it sits behind some. So both lists run: DISPLAY_SEL for the behaviour and PLATE_SEL for classes whose name is the confession.

Verified against both decks: 7 plates across 5 frames of the shipped deck, 0 on the reference build. A 0.22 wash and a gradient are still allowed, and the furniture is excluded because TXLAYOUT owns its own treatment.


Generated by Claude Code

…eported clean

Codex reviewed the deck-chassis change and every one of its eight findings held up.

THE CONTINUITY MANDATE WAS DECORATION. min_continuity_devices went into the rotation
table and nothing read it, which is GATE_LESSONS' oldest shape committed inside the
same change that added a gate against that shape. TXLAYOUT.check now takes the
device list, layout_check reads 'CONTINUITY: <DEVICE>, <DEVICE>' off the storyboard
and binds under --require, and five self-test cases make the key mean something.

THE FINISH CHECK WAS A SUBSTRING TEST. It passed on a commented-out call and on a
frame that graded and then kept drawing, both of which ship artwork the grade never
saw. It now finds executable calls and refuses any canvas mutation after the last one.

THE PLATE CHECK KNEW SIX CLASS NAMES. A hex background on .hook, an inline style, or
a short-hex alpha all walked past it. It now parses every rule and judges the fill's
opacity on any selector that reaches display type, and keeps the names too, because
dropping them lost the original .plate catch: that class carries no display type of
its own, it sits behind some.

THE NEW GATES WERE ONLY EVER POINTED AT A FIXTURE. guards.yml self-tested them and
ran them against examples/, so a run that skipped its Phase 11 commands could ship a
chassis-less strobing deck under a green board. Both are registered in shipped_check
now, with a since-date because no deck before today has a chassis and a gate does not
judge the work that produced it.

A CHASSIS NAMED FOR ITS WORLD ALONE COLLIDES. A later deck picking the same world
would overwrite it while the earlier run's archived slides still point at that path,
so re-checking or reproducing that run would render a different light, ramp and grade.
Chassis files carry their run's date and an undated name is refused.

THE TREATMENT DIRECTORS WERE STALE TOO, and they run BEFORE the flow critic and seed
the dossiers, so overriding only the critic left the plan itself aimed at the old
rule. Phase 9 hands them the current rule and has them name the deck's devices.

THE PANEL TRUSTED THE JUDGES' SCHEMA. Three judges all returning the previous six
criterion card agree perfectly with each other, so the panel weighted a rubric with
deck_coherence missing and reported a clean per-criterion median. It now validates
names and weights against the rubric per card, and the historical replays pass the
rubric of their own day so a 2026-08-19 assertion does not require a criterion that
did not exist then.

THE EMAIL KEPT A STALE BAR. gmail_draft defaulted to 7.0 with the rubric at 8.0 and
its own usage example omits the flag, so a 7.4 would have been captioned Shipped. It
reads the rubric now.
… from

CI's gates job was red on port_audit's residue check and the failure was mine:
config/carousel/deck_coherence.json names the sibling repo, and residue refuses
Alaska names outside its allowlist.

LOCALLY THIS LOOKED LIKE A DIFFERENT FAILURE, which is worth writing down. With the
sibling checkout on disk, parity runs and fails on three pre-existing brand.yaml
gaps, and residue was buried under it. In CI the sibling is absent, so parity SKIPS
and residue was the only failure. Two environments, one script, different answers,
and the local one hid the half that was actually mine. Same shape as the local suite
and CI disagreeing about email_check on 2026-08-26.

The file is allowlisted by exact path, alongside the two instrument design records
and for a narrower reason than theirs: every ceiling in it was derived from a 58 deck
corpus and the file states which corpus, how it was measured and what the subject
measured against it, because the law this project publishes is that a figure can be
recomputed from the same inputs. A derivation that will not name its inputs is a
number with a story attached, and the next context that wants a looser ceiling talks
itself into one in an afternoon. 'The sibling product' would not have done: this repo
has four siblings and two of them are Texan.

Only that one path is exempt, so the residue rule still protects what it is for, and
the ledgers still may not name Alaska at all.

Copy link
Copy Markdown
Owner Author

Correcting myself on port_audit: in CI that failure was this PR's

My earlier comment listed port_audit among the failures "also red on main and also not this PR's". That was right about what I measured and wrong about what CI was failing on, and the difference matters because I used it to stand down.

What I measured: locally, with the sibling checkout present, port_audit fails its parity check on three pre-existing config/brand.yaml gaps, identically on a clean origin/main worktree. That part still holds.

What CI was failing on: residue, not parity. In CI the sibling repo is not on disk, so parity SKIPs (reference /home/user/alaskaaicarousels/config/brand.yaml not on disk) and residue was the only failure in the job:

[FAIL] residue
config/carousel/deck_coherence.json:8: 'alaskaaicarousels' in: "reference": {"product": "alaskaaicarousels", ...}

That is mine. config/carousel/deck_coherence.json names the sibling repo, and residue refuses Alaska names outside its allowlist. Locally it was buried under the parity failure, so I read the job as the base's and never saw the half I had caused.

Two environments, one script, different answers, and the local one hid the part that was mine. That is the same shape as the local suite and CI disagreeing about email_check on 2026-08-26, which CLAUDE.md already writes up as "a local gate run and CI are not the same check". I had read the local run and stopped.

Fixed in 9796e6b. The file is allowlisted by exact path, alongside the two instrument design records, for a narrower reason than theirs: every ceiling in it was derived from a 58 deck corpus and the file states which corpus, how it was measured and what the subject measured against it, because the law this project publishes is that a figure can be recomputed from the same inputs. A derivation that will not name its inputs is a number with a story attached. "The sibling product" would not have done, because this repo has four siblings and two of them are Texan. Only that one path is exempt, so the ledgers still may not name Alaska at all.

port_audit now reports residue: PASS and the only remaining failure is parity, which is the pre-existing one and which skips in CI.

Still not this PR's, unchanged: browser-render (page_ground, red on main for its last six runs) and freshness (site_fresh_check, where the sanctioned rebuild deletes 324 published files).


Generated by Claude Code

This is the same defect as the shipped_check one, in a worse place, and I fixed the
first and missed the second inside one change.

scripts/site/site_context.py decides which runs become ARTICLES by calling
run_complete.threshold() ONCE and applying today's bar to every deck ever published.
Raising the bar to 8.0 therefore dropped every deck between 7.09 and 7.58: fifteen
article pages and about three hundred media files, the last three decks among them.

IT SURFACED AS A REBUILD THAT WANTED TO DELETE THEM, and the first reading of that
was that the site builder had a pre-existing bug. It did not. The deletions were this
change's, and calling them pre-existing repeats the attribution mistake this run had
already made once today on port_audit's residue check.

BAR_HISTORY and bar_in_force move to run_complete, which both readers already import,
because two copies of a rule about history is how history gets two versions.
bar_for_run reads a deck's OWN recorded threshold first and falls back to the bar in
force on its date, and site_context asks it per run rather than once per build.

WHAT REMAINS IS NOT MINE AND IS LEGITIMATE. site_fresh_check still reports two
MISSING article pages, 2026-09-03 and 2026-09-09, and the EXTRA list is now empty.
Those two decks scored 6.762 and 6.714 against the 6.8 bar they were judged under,
both hit rounds 5 against a cap of 5, and neither carries a hard fail, so
run_complete's round cap path authorises them exactly as written: past the cap a deck
with no hard fail is finished and ships at whatever it scored. They became
publishable when they shipped and docs/ was never rebuilt. The cure is a rebuild that
adds two pages and deletes nothing.
…ilt for

site_fresh_check has been red on main for a while and this is the whole of it: the
builder produces article pages for 2026-09-03 and 2026-09-09 and docs/ does not carry
them. Both scored under the 6.8 bar they were judged against, 6.762 and 6.714, and
both reached rounds 5 against a cap of 5 with no hard fail, so run_complete's round
cap path authorises them exactly as written. They became publishable the day they
shipped and the site was never rebuilt.

ADD ONLY, AND THAT IS THE POINT. 6 new files and 34 modified index and sitemap pages,
and ZERO deletions. Before the previous commit fixed the per-run bar this same
rebuild wanted to delete 324 files, fifteen published article pages among them, and
that is why it was reverted rather than committed the first time. The gate now
reports the site fresh, 1580 files matching a rebuild byte for byte.

docs/ is generated and any actor may trigger a rebuild. Nothing here was hand edited.
browser-render has been red on main for its last six guards runs on one assertion,
"the horizon is warmer than the sky above it", reporting top r-b -7 and horizon -14.

THE SKY WAS NEVER THE PROBLEM. Measured down the centre column at 1440x900, the warm
band peaks at y 770 with r-b +6 against the top's -8, so the dusk horizon is doing
exactly what theme.py's comments say it should. The pair sampled x 720, y 880, and
the front page grew an ask box into that column: elementFromPoint at that point
returns the box's own INPUT, which paints a cool fill, so the check was reading a
form control and calling it the sky.

This file's own header says these checks sample where content is NOT, and the other
four points are gutters for that reason. The pair now samples the right gutter, the
same x the spots table already uses, where the same band measures +3 against -8.

AND IT ASSERTS THE POINT IS SKY BEFORE IT MEASURES IT, because moving a hardcoded
coordinate to another hardcoded coordinate is the same bug waiting for the next
layout change. If anything in main, the masthead, the footer or the ask box is ever
under that point again, the check says so in those words rather than reporting the
sky as cold.

Nothing in the sky, the theme or the page was changed.
…h3ekki

# Conflicts:
#	docs/index.html
#	tests/page_ground.mjs
@Talonsturgill
Talonsturgill merged commit da095c8 into main Sep 17, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant