From 94b1d062a9a4a5fca5b682386e3e75f4c1e4f7b0 Mon Sep 17 00:00:00 2001 From: "prompt.ac/@jeffrey" Date: Mon, 10 Aug 2026 18:24:46 -0700 Subject: [PATCH] Free the frame name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit disks/ is one flat folder and a file name is a URL, so every recovered brush spends a word out of the whole site's namespace. Nine of these took generic English — build, banner, aura, triangle, ellipse, breathe, vignette, rainbow, frame — and frame is the one worth getting back: it is the obvious name for anything about framing, and the fleet already has a frame tool meaning something else. So the piece is frames, which is what Construct called the object anyway. The operation keeps its recovered name: nopaintProposal.slug is still "frame" and the catalogs are untouched, while brush.slug now says "frames" so a score names the piece you would rerun. Box already worked this way, owning "rect". The test that asserted file name == slug now states the split instead of enforcing it. Co-Authored-By: Claude Opus 5 (1M context) --- docs/nopaint-construct-migration-ledger.md | 17 ++++++++++- .../disks/{frame.mjs => frames.mjs} | 11 ++++++-- .../aesthetic.computer/disks/nopaint.mjs | 2 +- .../lib/nopaint-construct-sprites.mjs | 4 ++- .../tests/nopaint-construct-sprites.test.mjs | 28 ++++++++++++++++--- 5 files changed, 52 insertions(+), 10 deletions(-) rename system/public/aesthetic.computer/disks/{frame.mjs => frames.mjs} (57%) diff --git a/docs/nopaint-construct-migration-ledger.md b/docs/nopaint-construct-migration-ledger.md index a2a39372c7..494f348a9c 100644 --- a/docs/nopaint-construct-migration-ledger.md +++ b/docs/nopaint-construct-migration-ledger.md @@ -44,10 +44,25 @@ of the Construct implementation. `stamp` was on that list until the existing `disks/stamp.mjs` grew a `nopaintProposal` beside its own brush, the way `box` already had. +## Piece names versus operation names + +`disks/` is one flat folder and a file name is a URL, so every recovered brush +spends a word out of the whole site's namespace. Usually that is fine and the +piece takes the operation's name. Twice it is not: + +- `box.mjs` owns the operation `rect`. +- `frames.mjs` owns the operation `frame` — `frame` is too useful a word to + spend on one brush, and `Frames` is what Construct called the object. + +The operation name is what `nopaintProposal.slug` and the recovered catalogs +use; the piece name is what `brush.slug` records, so a score always names the +piece you would rerun. Nothing resolves `brush.slug` to a disk automatically — +it is provenance — but keep it pointing at a real piece. + ## Pieces that own their slug Every recovered brush now has one: `bubbles`, `walker`, `dark-window`, `line`, -`box`, `stamp`, `frame`, `caterpillar`, `softy`, `wafer`, `grid-worm`, +`box`, `stamp`, `frames`, `caterpillar`, `softy`, `wafer`, `grid-worm`, `triangle`, `ellipse`, `rainbow`, `breathe`, `vignette`, `aura`, `build`, and `banner` are real AC pieces that export their own `nopaintProposal`; `disks/nopaint.mjs` imports each one and registers it after the fallback diff --git a/system/public/aesthetic.computer/disks/frame.mjs b/system/public/aesthetic.computer/disks/frames.mjs similarity index 57% rename from system/public/aesthetic.computer/disks/frame.mjs rename to system/public/aesthetic.computer/disks/frames.mjs index 7f8c6cd84c..b395c6bce9 100644 --- a/system/public/aesthetic.computer/disks/frame.mjs +++ b/system/public/aesthetic.computer/disks/frames.mjs @@ -1,13 +1,18 @@ -// Frame, 26.08.10 +// Frames, 26.08.10 // The eleven original No Paint borders as a standalone AC brush. +// +// The piece is `frames`, not `frame`: `frame` is too useful a word to spend on +// one brush, and Frames is what Construct called the object anyway. The No +// Paint operation keeps its recovered name `frame`, the way Box keeps `rect` — +// see nopaintProposal.slug versus brush.slug. import { createNoPaintBrushPiece } from "../lib/nopaint-brush-piece.mjs"; import { frameProposal as nopaintProposal } from "../lib/nopaint-construct-sprites.mjs"; const system = "nopaint"; const piece = createNoPaintBrushPiece(nopaintProposal, { - title: "Frame", - desc: "Paint with the eleven recovered No Paint borders, knocking every five seconds.", + title: "Frames", + desc: "Paint with the eleven recovered No Paint borders, knocking every second.", }); export const { boot, sim, paint, bake, meta } = piece; diff --git a/system/public/aesthetic.computer/disks/nopaint.mjs b/system/public/aesthetic.computer/disks/nopaint.mjs index efd5a03f71..5e4012bef4 100644 --- a/system/public/aesthetic.computer/disks/nopaint.mjs +++ b/system/public/aesthetic.computer/disks/nopaint.mjs @@ -16,7 +16,7 @@ import { nopaintProposal as bubblesProposal } from "./bubbles.mjs"; import { nopaintProposal as darkWindowProposal } from "./dark-window.mjs"; import { nopaintProposal as walkerProposal } from "./walker.mjs"; import { nopaintProposal as stampProposal } from "./stamp.mjs"; -import { nopaintProposal as frameProposal } from "./frame.mjs"; +import { nopaintProposal as frameProposal } from "./frames.mjs"; import { nopaintProposal as caterpillarProposal } from "./caterpillar.mjs"; import { nopaintProposal as softyProposal } from "./softy.mjs"; import { nopaintProposal as waferProposal } from "./wafer.mjs"; diff --git a/system/public/aesthetic.computer/lib/nopaint-construct-sprites.mjs b/system/public/aesthetic.computer/lib/nopaint-construct-sprites.mjs index 729601f454..6b997b64a4 100644 --- a/system/public/aesthetic.computer/lib/nopaint-construct-sprites.mjs +++ b/system/public/aesthetic.computer/lib/nopaint-construct-sprites.mjs @@ -139,7 +139,9 @@ export const frameProposal = frozen({ // and let the second-by-second cycle decide what you kept. const start = FRAME_START_INDEX; return frozen({ ...base, kind: "frame", start, width, height, - brush: frozen({ slug: "frame", params: frozen([String(start)]), colon: frozen([]), + // The operation is `frame`; the piece that owns it is `frames`, the way + // Box owns `rect`. + brush: frozen({ slug: "frames", params: frozen([String(start)]), colon: frozen([]), parameters: frozen({ start, frames: frameFrames.length, cycleSeconds: 1 }) }) }); }, render(api, score, tick) { diff --git a/system/tests/nopaint-construct-sprites.test.mjs b/system/tests/nopaint-construct-sprites.test.mjs index 75e683e215..50ba632ae5 100644 --- a/system/tests/nopaint-construct-sprites.test.mjs +++ b/system/tests/nopaint-construct-sprites.test.mjs @@ -108,17 +108,37 @@ test("Frame advances one border per recovered one second cycle", () => { cropAt(FRAME_CYCLE_TICKS * frameFrames.length)[3].crop); }); -test("Bubbles, Walker, Dark Window, and Frame are standalone No Paint piece modules", async () => { - for (const slug of ["bubbles", "walker", "dark-window", "frame"]) { - const piece = await import(`../public/aesthetic.computer/disks/${slug}.mjs`); +// A piece's file name is its URL; a proposal's slug is the recovered Construct +// operation. They are usually the same word, and are allowed not to be — Box +// owns `rect`, Frames owns `frame`. What must always hold is that the score +// points back at the piece through brush.slug. +test("the sprite pieces are standalone No Paint modules", async () => { + const pieces = { bubbles: "bubbles", walker: "walker", + "dark-window": "dark-window", frames: "frame" }; + for (const [file, operation] of Object.entries(pieces)) { + const piece = await import(`../public/aesthetic.computer/disks/${file}.mjs`); assert.equal(piece.system, "nopaint"); - assert.equal(piece.nopaintProposal.slug, slug); + assert.equal(piece.nopaintProposal.slug, operation); assert.equal(typeof piece.paint, "function"); assert.equal(typeof piece.bake, "function"); assert.equal(typeof piece.meta, "function"); } }); +test("Frames owns the frame operation and points its score back at itself", async () => { + const piece = await import("../public/aesthetic.computer/disks/frames.mjs"); + const { COMPATIBLE_BRUSHES } = await import( + "../public/aesthetic.computer/disks/nopaint.mjs"); + assert.equal(piece.nopaintProposal.slug, "frame", "the operation keeps its name"); + assert.equal(COMPATIBLE_BRUSHES.get("frame"), piece.nopaintProposal); + const score = piece.nopaintProposal.generate({ + base: Object.freeze({ color: Object.freeze([1, 2, 3, 4]) }), + width: 64, height: 64, + }); + assert.equal(score.kind, "frame"); + assert.equal(score.brush.slug, "frames", "the score names the piece to rerun"); +}); + test("Line and Box publish the proposal contracts consumed by No Paint 3", async () => { const line = await import("../public/aesthetic.computer/disks/line.mjs"); const box = await import("../public/aesthetic.computer/disks/box.mjs"); -- 2.51.2