Monorepo for Aesthetic.Computer aesthetic.computer
4
fork

Configure Feed

Select the types of activity you want to include in your feed.

papers/arxiv-penrose: add Penrose-driven illustration pipeline

New paper that demonstrates a data-driven diagram pipeline for the AC
papers stack: an AC-specific Penrose domain (Piece, Library, Function,
Repo, Category) with three styles (lineage, set membership, library
graph), plus build-figures.mjs that introspects the live repo (378
disks, 78 lib modules, 12 KidLisp categories, 4 successive repos) and
emits matching .substance files. Ships deterministic TikZ fallbacks so
the paper compiles without @penrose/roger on PATH.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

+1477
+1
papers/SCORE.md
··· 40 40 41 41 | Paper | Format | PDF | Source | 42 42 |-------|--------|-----|--------| 43 + | Diagrams from Data: A Penrose Pipeline for AC Illustrations | arXiv (LaTeX) | `arxiv-penrose/penrose.pdf` | `arxiv-penrose/penrose.tex` | 43 44 | Where the Microseconds Go: Input and Audio Latency in AC Native OS | arXiv (LaTeX, 6pp) | `arxiv-latency/latency.pdf` | `arxiv-latency/latency.tex` | 44 45 | Aesthetic Computer Demo (C&C 2026) | ACM Demo (LaTeX) | `cc-demo-2026/demo.pdf` | `cc-demo-2026/demo.tex` | 45 46 | The URL Tradition | arXiv (LaTeX) | `arxiv-url-tradition/url-tradition.pdf` | `arxiv-url-tradition/url-tradition.tex` |
+1
papers/arxiv-penrose/ac-paper-layout.sty
··· 1 + ../ac-paper-layout.sty
+377
papers/arxiv-penrose/build-figures.mjs
··· 1 + #!/usr/bin/env node 2 + // build-figures.mjs — Generate Penrose .substance files from real AC data, 3 + // then compile each trio (domain + style + substance) to SVG via roger. 4 + // 5 + // Usage: 6 + // node build-figures.mjs # generate substance + compile SVGs 7 + // node build-figures.mjs --substance # generate substance only 8 + // node build-figures.mjs --no-compile # alias for --substance 9 + // 10 + // Compilation needs `npx @penrose/roger` available on PATH. 11 + // On first run npx will download the package; subsequent runs are cached. 12 + 13 + import { 14 + readdirSync, 15 + writeFileSync, 16 + readFileSync, 17 + existsSync, 18 + mkdirSync, 19 + } from "node:fs"; 20 + import { join, dirname, basename } from "node:path"; 21 + import { fileURLToPath } from "node:url"; 22 + import { execFileSync } from "node:child_process"; 23 + 24 + const HERE = dirname(fileURLToPath(import.meta.url)); 25 + const ROOT = join(HERE, "..", ".."); 26 + const DISKS = join(ROOT, "system", "public", "aesthetic.computer", "disks"); 27 + const LIB = join(ROOT, "system", "public", "aesthetic.computer", "lib"); 28 + const KIDLISP = join(LIB, "kidlisp.mjs"); 29 + 30 + const FIG = join(HERE, "figures"); 31 + const PEN = join(FIG, "penrose"); 32 + const SUB = join(PEN, "substance"); 33 + mkdirSync(SUB, { recursive: true }); 34 + 35 + const args = new Set(process.argv.slice(2)); 36 + const compile = !(args.has("--substance") || args.has("--no-compile")); 37 + 38 + // ---------- 1. Live data ---------- 39 + 40 + function listFiles(dir, exts) { 41 + if (!existsSync(dir)) return []; 42 + return readdirSync(dir) 43 + .filter((f) => exts.some((e) => f.endsWith(e))) 44 + .map((f) => ({ name: f, path: join(dir, f) })); 45 + } 46 + 47 + const pieces = listFiles(DISKS, [".mjs", ".lisp"]); 48 + const libs = listFiles(LIB, [".mjs"]); 49 + 50 + // Heuristic library import scan over a sample of pieces — full repo would 51 + // take too long for a paper-build pass, so we read the first ~120 pieces 52 + // (sorted by size) which gives a representative graph. 53 + function importsOf(piecePath) { 54 + try { 55 + const txt = readSync(piecePath); 56 + const re = /from\s+["']([^"']+\/lib\/[^"']+)["']/g; 57 + const out = new Set(); 58 + let m; 59 + while ((m = re.exec(txt))) { 60 + const lib = basename(m[1]).replace(/\.mjs$/, ""); 61 + out.add(lib); 62 + } 63 + return [...out]; 64 + } catch { 65 + return []; 66 + } 67 + } 68 + function readSync(p) { 69 + return readFileSync(p, "utf8"); 70 + } 71 + 72 + // ---------- 2. KidLisp categories ---------- 73 + // 74 + // The canonical 12 categories live in kidlisp/README.md. We could parse 75 + // them out of kidlisp.mjs, but the source uses inconsistent banner styles, 76 + // so we ship the documented taxonomy and only verify each key still exists 77 + // as a string-literal property in the evaluator. 78 + function kidlispCategories() { 79 + // Categories are documented in kidlisp/README.md. Probe kidlisp.mjs for 80 + // each key — keys that resolve in the evaluator are emitted; the rest 81 + // are kept but flagged so the figure still shows the documented surface. 82 + const txt = readSync(KIDLISP); 83 + const present = (k) => { 84 + const esc = k.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); 85 + return ( 86 + new RegExp(`["']${esc}["']\\s*:`).test(txt) || 87 + new RegExp(`\\b${esc.replace(/[^A-Za-z0-9_]/g, "")}\\s*\\(`).test(txt) 88 + ); 89 + }; 90 + let verified = 0; 91 + const out = CANONICAL_KIDLISP_CATEGORIES.map((c) => { 92 + const keys = c.keys.slice(0, 5); 93 + verified += keys.filter(present).length; 94 + return { name: c.name, keys }; 95 + }); 96 + console.log( 97 + `[penrose] kidlisp: verified ${verified}/${out.reduce((n, c) => n + c.keys.length, 0)} canonical keys present in kidlisp.mjs`, 98 + ); 99 + return out; 100 + } 101 + const CANONICAL_KIDLISP_CATEGORIES = [ 102 + { name: "math", keys: ["+", "-", "*", "/", "mod"] }, 103 + { name: "color", keys: ["red", "blue", "ink", "wipe"] }, 104 + { name: "graphics", keys: ["line", "box", "circle", "plot"] }, 105 + { name: "text", keys: ["write", "type", "len"] }, 106 + { name: "audio", keys: ["tone", "beat", "note"] }, 107 + { name: "time", keys: ["sec", "frame", "wait"] }, 108 + { name: "control", keys: ["if", "do", "later"] }, 109 + { name: "input", keys: ["pen", "tap", "key"] }, 110 + { name: "list", keys: ["map", "filter", "range"] }, 111 + { name: "random", keys: ["?", "rand", "pick"] }, 112 + { name: "geom", keys: ["dist", "lerp", "norm"] }, 113 + { name: "system", keys: ["jump", "params", "hop"] }, 114 + ]; 115 + 116 + // ---------- 3. Repo lineage (static — pulled from arxiv-archaeology) ---------- 117 + const repos = [ 118 + { id: "system_ac", label: "system-ac", date: "2021-08" }, 119 + { id: "disks_ac", label: "disks-ac", date: "2021-10" }, 120 + { id: "ac_2022", label: "2022.aesthetic.c", date: "2021-12" }, 121 + { id: "ac_now", label: "aesthetic-comp.", date: "2022-12" }, 122 + ]; 123 + 124 + // ---------- 4. Emit substance files ---------- 125 + 126 + function escId(s) { 127 + return s.replace(/[^A-Za-z0-9_]/g, "_"); 128 + } 129 + function quote(s) { 130 + return JSON.stringify(s); 131 + } 132 + 133 + // Figure 1: lineage.substance 134 + { 135 + const lines = [ 136 + `-- generated by build-figures.mjs ${new Date().toISOString()}`, 137 + ]; 138 + for (const r of repos) { 139 + lines.push(`Repo ${r.id}`); 140 + lines.push(`AutoLabel ${r.id} ${quote(r.label)}`); 141 + lines.push(`Override ${r.id}.label = ${quote(r.label)}`); 142 + lines.push(`Override ${r.id}.date = ${quote(r.date)}`); 143 + } 144 + for (let i = 0; i < repos.length - 1; i++) { 145 + lines.push(`Succeeds(${repos[i].id}, ${repos[i + 1].id})`); 146 + } 147 + writeFileSync(join(SUB, "lineage.substance"), lines.join("\n") + "\n"); 148 + } 149 + 150 + // Figure 2: categories.substance — KidLisp 151 + { 152 + const cats = kidlispCategories(); 153 + const lines = [ 154 + `-- generated by build-figures.mjs — ${cats.length} categories`, 155 + `-- source: ${KIDLISP.slice(KIDLISP.indexOf("system/"))}`, 156 + ]; 157 + let fnSeq = 0; 158 + for (const c of cats) { 159 + const cid = "cat_" + escId(c.name); 160 + lines.push(`Category ${cid}`); 161 + lines.push(`Override ${cid}.label = ${quote(c.name)}`); 162 + for (const k of c.keys) { 163 + const fid = `fn_${++fnSeq}`; 164 + lines.push(`Function ${fid}`); 165 + lines.push(`Override ${fid}.label = ${quote(k)}`); 166 + lines.push(`Member(${fid}, ${cid})`); 167 + } 168 + } 169 + writeFileSync(join(SUB, "categories.substance"), lines.join("\n") + "\n"); 170 + } 171 + 172 + // Figure 3: clusters.substance — Pieces & Libraries 173 + const clusterData = (() => { 174 + // Stable alphabetical sample so the figure is reproducible across machines. 175 + const sample = pieces 176 + .filter((p) => !p.name.startsWith("test-") && !p.name.startsWith("_")) 177 + .sort((a, b) => a.name.localeCompare(b.name)) 178 + .slice(0, 24); 179 + 180 + // Pick the 8 most-imported libraries across the sample. 181 + const libCount = new Map(); 182 + const pieceImports = new Map(); 183 + for (const p of sample) { 184 + const ims = importsOf(p.path); 185 + pieceImports.set(p.name, ims); 186 + for (const i of ims) libCount.set(i, (libCount.get(i) || 0) + 1); 187 + } 188 + const topLibs = [...libCount.entries()] 189 + .sort((a, b) => b[1] - a[1]) 190 + .slice(0, 8) 191 + .map(([name]) => name); 192 + const topSet = new Set(topLibs); 193 + 194 + const lines = [ 195 + `-- generated by build-figures.mjs — ${sample.length} pieces, ${topLibs.length} libs`, 196 + ]; 197 + for (const lib of topLibs) { 198 + const lid = "lib_" + escId(lib); 199 + lines.push(`Library ${lid}`); 200 + lines.push(`Override ${lid}.label = ${quote(lib)}`); 201 + } 202 + for (const p of sample) { 203 + const pid = "pc_" + escId(p.name.replace(/\.(mjs|lisp)$/, "")); 204 + lines.push(`Piece ${pid}`); 205 + lines.push(`Override ${pid}.label = ${quote(p.name.replace(/\.(mjs|lisp)$/, ""))}`); 206 + for (const im of pieceImports.get(p.name) || []) { 207 + if (!topSet.has(im)) continue; 208 + lines.push(`Imports(${pid}, lib_${escId(im)})`); 209 + } 210 + } 211 + writeFileSync(join(SUB, "clusters.substance"), lines.join("\n") + "\n"); 212 + return { sample, topLibs, pieceImports }; 213 + })(); 214 + 215 + console.log("[penrose] substance files written to", SUB); 216 + console.log("[penrose] pieces:", pieces.length, "libs:", libs.length); 217 + 218 + // ---------- 4b. TikZ fallback figures ---------- 219 + // Emit deterministic TikZ pictures so the LaTeX paper compiles without 220 + // requiring @penrose/roger. The Penrose pipeline (step 5) can later overwrite 221 + // these with optimized SVG/PDF renders. 222 + 223 + function texEsc(s) { 224 + return String(s).replace(/[\\{}_$&%#^~]/g, (c) => 225 + ({ "\\": "\\textbackslash{}", "{": "\\{", "}": "\\}", _: "\\_", $: "\\$", "&": "\\&", "%": "\\%", "#": "\\#", "^": "\\^{}", "~": "\\~{}" }[c]), 226 + ); 227 + } 228 + 229 + // Lineage TikZ 230 + { 231 + const lines = [ 232 + "% generated by build-figures.mjs — DO NOT EDIT", 233 + "\\begin{tikzpicture}[x=1mm,y=1mm]", 234 + " \\definecolor{lpink}{RGB}{180,72,135}", 235 + " \\definecolor{lpurple}{RGB}{120,80,180}", 236 + " \\definecolor{ldark}{RGB}{64,56,74}", 237 + " \\definecolor{lgray}{RGB}{119,119,119}", 238 + ]; 239 + const xs = repos.map((_, i) => 18 + i * 36); 240 + for (let i = 0; i < repos.length - 1; i++) { 241 + lines.push( 242 + ` \\draw[->,>=stealth,line width=0.8pt,lpurple] (${xs[i] + 4},14) -- (${xs[i + 1] - 4},14);`, 243 + ); 244 + } 245 + for (let i = 0; i < repos.length; i++) { 246 + const r = repos[i]; 247 + lines.push( 248 + ` \\fill[lpink] (${xs[i]},14) circle (3);`, 249 + ` \\draw[ldark,line width=0.5pt] (${xs[i]},14) circle (3);`, 250 + ` \\node[font=\\footnotesize\\color{ldark}] at (${xs[i]},22) {\\texttt{${texEsc(r.label)}}};`, 251 + ` \\node[font=\\scriptsize\\color{lgray}] at (${xs[i]},6) {${texEsc(r.date)}};`, 252 + ); 253 + } 254 + lines.push("\\end{tikzpicture}"); 255 + writeFileSync(join(FIG, "lineage.tex"), lines.join("\n") + "\n"); 256 + } 257 + 258 + // Categories TikZ — grid of category cells with function labels. 259 + { 260 + const cats = kidlispCategories(); 261 + const cols = 4; 262 + const cellW = 38, cellH = 28, padX = 4, padY = 6; 263 + const lines = [ 264 + "% generated by build-figures.mjs — DO NOT EDIT", 265 + "\\begin{tikzpicture}[x=1mm,y=1mm]", 266 + " \\definecolor{lpink}{RGB}{180,72,135}", 267 + " \\definecolor{lpurple}{RGB}{120,80,180}", 268 + " \\definecolor{ldark}{RGB}{64,56,74}", 269 + ]; 270 + cats.forEach((c, i) => { 271 + const col = i % cols; 272 + const row = Math.floor(i / cols); 273 + const x = padX + col * cellW; 274 + const y = -(padY + row * cellH); 275 + lines.push( 276 + ` \\draw[lpurple,fill=lpink!18,line width=0.4pt,rounded corners=1.4mm] (${x},${y}) rectangle (${x + cellW - 4},${y - cellH + 6});`, 277 + ` \\node[font=\\scriptsize\\bfseries\\color{ldark}] at (${x + (cellW - 4) / 2},${y - cellH + 9}) {${texEsc(c.name)}};`, 278 + ); 279 + c.keys.forEach((k, ki) => { 280 + const dx = x + 3 + (ki % 3) * 10.5; 281 + const dy = y - 5 - Math.floor(ki / 3) * 8; 282 + lines.push( 283 + ` \\fill[lpurple] (${dx},${dy}) circle (0.7);`, 284 + ` \\node[font=\\tiny\\color{ldark},anchor=west] at (${dx + 1.2},${dy}) {\\texttt{${texEsc(k)}}};`, 285 + ); 286 + }); 287 + }); 288 + lines.push("\\end{tikzpicture}"); 289 + writeFileSync(join(FIG, "categories.tex"), lines.join("\n") + "\n"); 290 + } 291 + 292 + // Clusters TikZ — concentric: libs inner ring, pieces outer ring; edges = imports. 293 + { 294 + const { sample, topLibs, pieceImports } = clusterData; 295 + const cx = 0, cy = 0, libR = 18, pieceR = 36; 296 + const libPos = new Map(); 297 + topLibs.forEach((lib, i) => { 298 + const a = (i / topLibs.length) * Math.PI * 2 - Math.PI / 2; 299 + libPos.set(lib, [cx + Math.cos(a) * libR, cy + Math.sin(a) * libR]); 300 + }); 301 + const piecePos = new Map(); 302 + sample.forEach((p, i) => { 303 + const a = (i / sample.length) * Math.PI * 2 - Math.PI / 2; 304 + piecePos.set(p.name, [cx + Math.cos(a) * pieceR, cy + Math.sin(a) * pieceR]); 305 + }); 306 + const lines = [ 307 + "% generated by build-figures.mjs — DO NOT EDIT", 308 + "\\begin{tikzpicture}[x=1mm,y=1mm]", 309 + " \\definecolor{lpink}{RGB}{180,72,135}", 310 + " \\definecolor{lpurple}{RGB}{120,80,180}", 311 + " \\definecolor{ldark}{RGB}{64,56,74}", 312 + ]; 313 + for (const p of sample) { 314 + const [px, py] = piecePos.get(p.name); 315 + for (const im of pieceImports.get(p.name) || []) { 316 + if (!libPos.has(im)) continue; 317 + const [lx, ly] = libPos.get(im); 318 + lines.push( 319 + ` \\draw[lpurple,opacity=0.35,line width=0.25pt] (${px.toFixed(2)},${py.toFixed(2)}) -- (${lx.toFixed(2)},${ly.toFixed(2)});`, 320 + ); 321 + } 322 + } 323 + for (const lib of topLibs) { 324 + const [x, y] = libPos.get(lib); 325 + lines.push( 326 + ` \\fill[lpurple] (${x.toFixed(2)},${y.toFixed(2)}) circle (1.6);`, 327 + ` \\node[font=\\tiny\\color{ldark},anchor=south] at (${x.toFixed(2)},${(y + 2).toFixed(2)}) {\\texttt{${texEsc(lib)}}};`, 328 + ); 329 + } 330 + for (const p of sample) { 331 + const [x, y] = piecePos.get(p.name); 332 + const label = p.name.replace(/\.(mjs|lisp)$/, ""); 333 + const onRight = x >= cx; 334 + lines.push( 335 + ` \\fill[lpink] (${x.toFixed(2)},${y.toFixed(2)}) circle (1.0);`, 336 + ` \\node[font=\\tiny\\color{ldark},anchor=${onRight ? "west" : "east"}] at (${(x + (onRight ? 1.5 : -1.5)).toFixed(2)},${y.toFixed(2)}) {\\texttt{${texEsc(label)}}};`, 337 + ); 338 + } 339 + lines.push("\\end{tikzpicture}"); 340 + writeFileSync(join(FIG, "clusters.tex"), lines.join("\n") + "\n"); 341 + } 342 + 343 + // ---------- 5. Compile via roger ---------- 344 + 345 + if (!compile) process.exit(0); 346 + 347 + const trios = [ 348 + { name: "lineage", style: "lineage.style", sub: "lineage.substance" }, 349 + { name: "categories", style: "categories.style", sub: "categories.substance" }, 350 + { name: "clusters", style: "clusters.style", sub: "clusters.substance" }, 351 + ]; 352 + 353 + for (const t of trios) { 354 + const out = join(FIG, `${t.name}.svg`); 355 + try { 356 + execFileSync( 357 + "npx", 358 + [ 359 + "--yes", 360 + "@penrose/roger", 361 + "trio", 362 + join(PEN, "ac.domain"), 363 + join(PEN, t.style), 364 + join(SUB, t.sub), 365 + "--out", 366 + out, 367 + ], 368 + { stdio: "inherit" }, 369 + ); 370 + console.log("[penrose]", t.name, "→", out); 371 + } catch (err) { 372 + console.warn( 373 + `[penrose] failed to compile ${t.name} via roger; ` + 374 + `the TikZ fallback in figures/${t.name}.tex remains the live render.`, 375 + ); 376 + } 377 + }
+110
papers/arxiv-penrose/figures/categories.tex
··· 1 + % generated by build-figures.mjs — DO NOT EDIT 2 + \begin{tikzpicture}[x=1mm,y=1mm] 3 + \definecolor{lpink}{RGB}{180,72,135} 4 + \definecolor{lpurple}{RGB}{120,80,180} 5 + \definecolor{ldark}{RGB}{64,56,74} 6 + \draw[lpurple,fill=lpink!18,line width=0.4pt,rounded corners=1.4mm] (4,-6) rectangle (38,-28); 7 + \node[font=\scriptsize\bfseries\color{ldark}] at (21,-25) {math}; 8 + \fill[lpurple] (7,-11) circle (0.7); 9 + \node[font=\tiny\color{ldark},anchor=west] at (8.2,-11) {\texttt{+}}; 10 + \fill[lpurple] (17.5,-11) circle (0.7); 11 + \node[font=\tiny\color{ldark},anchor=west] at (18.7,-11) {\texttt{-}}; 12 + \fill[lpurple] (28,-11) circle (0.7); 13 + \node[font=\tiny\color{ldark},anchor=west] at (29.2,-11) {\texttt{*}}; 14 + \fill[lpurple] (7,-19) circle (0.7); 15 + \node[font=\tiny\color{ldark},anchor=west] at (8.2,-19) {\texttt{/}}; 16 + \fill[lpurple] (17.5,-19) circle (0.7); 17 + \node[font=\tiny\color{ldark},anchor=west] at (18.7,-19) {\texttt{mod}}; 18 + \draw[lpurple,fill=lpink!18,line width=0.4pt,rounded corners=1.4mm] (42,-6) rectangle (76,-28); 19 + \node[font=\scriptsize\bfseries\color{ldark}] at (59,-25) {color}; 20 + \fill[lpurple] (45,-11) circle (0.7); 21 + \node[font=\tiny\color{ldark},anchor=west] at (46.2,-11) {\texttt{red}}; 22 + \fill[lpurple] (55.5,-11) circle (0.7); 23 + \node[font=\tiny\color{ldark},anchor=west] at (56.7,-11) {\texttt{blue}}; 24 + \fill[lpurple] (66,-11) circle (0.7); 25 + \node[font=\tiny\color{ldark},anchor=west] at (67.2,-11) {\texttt{ink}}; 26 + \fill[lpurple] (45,-19) circle (0.7); 27 + \node[font=\tiny\color{ldark},anchor=west] at (46.2,-19) {\texttt{wipe}}; 28 + \draw[lpurple,fill=lpink!18,line width=0.4pt,rounded corners=1.4mm] (80,-6) rectangle (114,-28); 29 + \node[font=\scriptsize\bfseries\color{ldark}] at (97,-25) {graphics}; 30 + \fill[lpurple] (83,-11) circle (0.7); 31 + \node[font=\tiny\color{ldark},anchor=west] at (84.2,-11) {\texttt{line}}; 32 + \fill[lpurple] (93.5,-11) circle (0.7); 33 + \node[font=\tiny\color{ldark},anchor=west] at (94.7,-11) {\texttt{box}}; 34 + \fill[lpurple] (104,-11) circle (0.7); 35 + \node[font=\tiny\color{ldark},anchor=west] at (105.2,-11) {\texttt{circle}}; 36 + \fill[lpurple] (83,-19) circle (0.7); 37 + \node[font=\tiny\color{ldark},anchor=west] at (84.2,-19) {\texttt{plot}}; 38 + \draw[lpurple,fill=lpink!18,line width=0.4pt,rounded corners=1.4mm] (118,-6) rectangle (152,-28); 39 + \node[font=\scriptsize\bfseries\color{ldark}] at (135,-25) {text}; 40 + \fill[lpurple] (121,-11) circle (0.7); 41 + \node[font=\tiny\color{ldark},anchor=west] at (122.2,-11) {\texttt{write}}; 42 + \fill[lpurple] (131.5,-11) circle (0.7); 43 + \node[font=\tiny\color{ldark},anchor=west] at (132.7,-11) {\texttt{type}}; 44 + \fill[lpurple] (142,-11) circle (0.7); 45 + \node[font=\tiny\color{ldark},anchor=west] at (143.2,-11) {\texttt{len}}; 46 + \draw[lpurple,fill=lpink!18,line width=0.4pt,rounded corners=1.4mm] (4,-34) rectangle (38,-56); 47 + \node[font=\scriptsize\bfseries\color{ldark}] at (21,-53) {audio}; 48 + \fill[lpurple] (7,-39) circle (0.7); 49 + \node[font=\tiny\color{ldark},anchor=west] at (8.2,-39) {\texttt{tone}}; 50 + \fill[lpurple] (17.5,-39) circle (0.7); 51 + \node[font=\tiny\color{ldark},anchor=west] at (18.7,-39) {\texttt{beat}}; 52 + \fill[lpurple] (28,-39) circle (0.7); 53 + \node[font=\tiny\color{ldark},anchor=west] at (29.2,-39) {\texttt{note}}; 54 + \draw[lpurple,fill=lpink!18,line width=0.4pt,rounded corners=1.4mm] (42,-34) rectangle (76,-56); 55 + \node[font=\scriptsize\bfseries\color{ldark}] at (59,-53) {time}; 56 + \fill[lpurple] (45,-39) circle (0.7); 57 + \node[font=\tiny\color{ldark},anchor=west] at (46.2,-39) {\texttt{sec}}; 58 + \fill[lpurple] (55.5,-39) circle (0.7); 59 + \node[font=\tiny\color{ldark},anchor=west] at (56.7,-39) {\texttt{frame}}; 60 + \fill[lpurple] (66,-39) circle (0.7); 61 + \node[font=\tiny\color{ldark},anchor=west] at (67.2,-39) {\texttt{wait}}; 62 + \draw[lpurple,fill=lpink!18,line width=0.4pt,rounded corners=1.4mm] (80,-34) rectangle (114,-56); 63 + \node[font=\scriptsize\bfseries\color{ldark}] at (97,-53) {control}; 64 + \fill[lpurple] (83,-39) circle (0.7); 65 + \node[font=\tiny\color{ldark},anchor=west] at (84.2,-39) {\texttt{if}}; 66 + \fill[lpurple] (93.5,-39) circle (0.7); 67 + \node[font=\tiny\color{ldark},anchor=west] at (94.7,-39) {\texttt{do}}; 68 + \fill[lpurple] (104,-39) circle (0.7); 69 + \node[font=\tiny\color{ldark},anchor=west] at (105.2,-39) {\texttt{later}}; 70 + \draw[lpurple,fill=lpink!18,line width=0.4pt,rounded corners=1.4mm] (118,-34) rectangle (152,-56); 71 + \node[font=\scriptsize\bfseries\color{ldark}] at (135,-53) {input}; 72 + \fill[lpurple] (121,-39) circle (0.7); 73 + \node[font=\tiny\color{ldark},anchor=west] at (122.2,-39) {\texttt{pen}}; 74 + \fill[lpurple] (131.5,-39) circle (0.7); 75 + \node[font=\tiny\color{ldark},anchor=west] at (132.7,-39) {\texttt{tap}}; 76 + \fill[lpurple] (142,-39) circle (0.7); 77 + \node[font=\tiny\color{ldark},anchor=west] at (143.2,-39) {\texttt{key}}; 78 + \draw[lpurple,fill=lpink!18,line width=0.4pt,rounded corners=1.4mm] (4,-62) rectangle (38,-84); 79 + \node[font=\scriptsize\bfseries\color{ldark}] at (21,-81) {list}; 80 + \fill[lpurple] (7,-67) circle (0.7); 81 + \node[font=\tiny\color{ldark},anchor=west] at (8.2,-67) {\texttt{map}}; 82 + \fill[lpurple] (17.5,-67) circle (0.7); 83 + \node[font=\tiny\color{ldark},anchor=west] at (18.7,-67) {\texttt{filter}}; 84 + \fill[lpurple] (28,-67) circle (0.7); 85 + \node[font=\tiny\color{ldark},anchor=west] at (29.2,-67) {\texttt{range}}; 86 + \draw[lpurple,fill=lpink!18,line width=0.4pt,rounded corners=1.4mm] (42,-62) rectangle (76,-84); 87 + \node[font=\scriptsize\bfseries\color{ldark}] at (59,-81) {random}; 88 + \fill[lpurple] (45,-67) circle (0.7); 89 + \node[font=\tiny\color{ldark},anchor=west] at (46.2,-67) {\texttt{?}}; 90 + \fill[lpurple] (55.5,-67) circle (0.7); 91 + \node[font=\tiny\color{ldark},anchor=west] at (56.7,-67) {\texttt{rand}}; 92 + \fill[lpurple] (66,-67) circle (0.7); 93 + \node[font=\tiny\color{ldark},anchor=west] at (67.2,-67) {\texttt{pick}}; 94 + \draw[lpurple,fill=lpink!18,line width=0.4pt,rounded corners=1.4mm] (80,-62) rectangle (114,-84); 95 + \node[font=\scriptsize\bfseries\color{ldark}] at (97,-81) {geom}; 96 + \fill[lpurple] (83,-67) circle (0.7); 97 + \node[font=\tiny\color{ldark},anchor=west] at (84.2,-67) {\texttt{dist}}; 98 + \fill[lpurple] (93.5,-67) circle (0.7); 99 + \node[font=\tiny\color{ldark},anchor=west] at (94.7,-67) {\texttt{lerp}}; 100 + \fill[lpurple] (104,-67) circle (0.7); 101 + \node[font=\tiny\color{ldark},anchor=west] at (105.2,-67) {\texttt{norm}}; 102 + \draw[lpurple,fill=lpink!18,line width=0.4pt,rounded corners=1.4mm] (118,-62) rectangle (152,-84); 103 + \node[font=\scriptsize\bfseries\color{ldark}] at (135,-81) {system}; 104 + \fill[lpurple] (121,-67) circle (0.7); 105 + \node[font=\tiny\color{ldark},anchor=west] at (122.2,-67) {\texttt{jump}}; 106 + \fill[lpurple] (131.5,-67) circle (0.7); 107 + \node[font=\tiny\color{ldark},anchor=west] at (132.7,-67) {\texttt{params}}; 108 + \fill[lpurple] (142,-67) circle (0.7); 109 + \node[font=\tiny\color{ldark},anchor=west] at (143.2,-67) {\texttt{hop}}; 110 + \end{tikzpicture}
+76
papers/arxiv-penrose/figures/clusters.tex
··· 1 + % generated by build-figures.mjs — DO NOT EDIT 2 + \begin{tikzpicture}[x=1mm,y=1mm] 3 + \definecolor{lpink}{RGB}{180,72,135} 4 + \definecolor{lpurple}{RGB}{120,80,180} 5 + \definecolor{ldark}{RGB}{64,56,74} 6 + \draw[lpurple,opacity=0.35,line width=0.25pt] (0.00,-36.00) -- (18.00,0.00); 7 + \draw[lpurple,opacity=0.35,line width=0.25pt] (9.32,-34.77) -- (0.00,-18.00); 8 + \draw[lpurple,opacity=0.35,line width=0.25pt] (18.00,-31.18) -- (0.00,-18.00); 9 + \draw[lpurple,opacity=0.35,line width=0.25pt] (25.46,-25.46) -- (0.00,18.00); 10 + \draw[lpurple,opacity=0.35,line width=0.25pt] (31.18,-18.00) -- (0.00,-18.00); 11 + \draw[lpurple,opacity=0.35,line width=0.25pt] (36.00,0.00) -- (0.00,-18.00); 12 + \draw[lpurple,opacity=0.35,line width=0.25pt] (31.18,18.00) -- (0.00,-18.00); 13 + \draw[lpurple,opacity=0.35,line width=0.25pt] (18.00,31.18) -- (0.00,-18.00); 14 + \draw[lpurple,opacity=0.35,line width=0.25pt] (9.32,34.77) -- (0.00,-18.00); 15 + \draw[lpurple,opacity=0.35,line width=0.25pt] (0.00,36.00) -- (0.00,-18.00); 16 + \draw[lpurple,opacity=0.35,line width=0.25pt] (-9.32,34.77) -- (0.00,-18.00); 17 + \draw[lpurple,opacity=0.35,line width=0.25pt] (-18.00,31.18) -- (0.00,-18.00); 18 + \draw[lpurple,opacity=0.35,line width=0.25pt] (-31.18,18.00) -- (0.00,-18.00); 19 + \draw[lpurple,opacity=0.35,line width=0.25pt] (-34.77,9.32) -- (-18.00,0.00); 20 + \fill[lpurple] (0.00,-18.00) circle (1.6); 21 + \node[font=\tiny\color{ldark},anchor=south] at (0.00,-16.00) {\texttt{abc123}}; 22 + \fill[lpurple] (18.00,0.00) circle (1.6); 23 + \node[font=\tiny\color{ldark},anchor=south] at (18.00,2.00) {\texttt{kidlisp}}; 24 + \fill[lpurple] (0.00,18.00) circle (1.6); 25 + \node[font=\tiny\color{ldark},anchor=south] at (0.00,20.00) {\texttt{gamepad-mappings}}; 26 + \fill[lpurple] (-18.00,0.00) circle (1.6); 27 + \node[font=\tiny\color{ldark},anchor=south] at (-18.00,2.00) {\texttt{chat}}; 28 + \fill[lpink] (0.00,-36.00) circle (1.0); 29 + \node[font=\tiny\color{ldark},anchor=west] at (1.50,-36.00) {\texttt{\$}}; 30 + \fill[lpink] (9.32,-34.77) circle (1.0); 31 + \node[font=\tiny\color{ldark},anchor=west] at (10.82,-34.77) {\texttt{0}}; 32 + \fill[lpink] (18.00,-31.18) circle (1.0); 33 + \node[font=\tiny\color{ldark},anchor=west] at (19.50,-31.18) {\texttt{1}}; 34 + \fill[lpink] (25.46,-25.46) circle (1.0); 35 + \node[font=\tiny\color{ldark},anchor=west] at (26.96,-25.46) {\texttt{1v1}}; 36 + \fill[lpink] (31.18,-18.00) circle (1.0); 37 + \node[font=\tiny\color{ldark},anchor=west] at (32.68,-18.00) {\texttt{2}}; 38 + \fill[lpink] (34.77,-9.32) circle (1.0); 39 + \node[font=\tiny\color{ldark},anchor=west] at (36.27,-9.32) {\texttt{3-kidlisp-tests}}; 40 + \fill[lpink] (36.00,0.00) circle (1.0); 41 + \node[font=\tiny\color{ldark},anchor=west] at (37.50,0.00) {\texttt{3}}; 42 + \fill[lpink] (34.77,9.32) circle (1.0); 43 + \node[font=\tiny\color{ldark},anchor=west] at (36.27,9.32) {\texttt{3x3}}; 44 + \fill[lpink] (31.18,18.00) circle (1.0); 45 + \node[font=\tiny\color{ldark},anchor=west] at (32.68,18.00) {\texttt{4}}; 46 + \fill[lpink] (25.46,25.46) circle (1.0); 47 + \node[font=\tiny\color{ldark},anchor=west] at (26.96,25.46) {\texttt{404}}; 48 + \fill[lpink] (18.00,31.18) circle (1.0); 49 + \node[font=\tiny\color{ldark},anchor=west] at (19.50,31.18) {\texttt{5}}; 50 + \fill[lpink] (9.32,34.77) circle (1.0); 51 + \node[font=\tiny\color{ldark},anchor=west] at (10.82,34.77) {\texttt{6}}; 52 + \fill[lpink] (0.00,36.00) circle (1.0); 53 + \node[font=\tiny\color{ldark},anchor=west] at (1.50,36.00) {\texttt{7}}; 54 + \fill[lpink] (-9.32,34.77) circle (1.0); 55 + \node[font=\tiny\color{ldark},anchor=east] at (-10.82,34.77) {\texttt{8}}; 56 + \fill[lpink] (-18.00,31.18) circle (1.0); 57 + \node[font=\tiny\color{ldark},anchor=east] at (-19.50,31.18) {\texttt{9}}; 58 + \fill[lpink] (-25.46,25.46) circle (1.0); 59 + \node[font=\tiny\color{ldark},anchor=east] at (-26.96,25.46) {\texttt{a-star}}; 60 + \fill[lpink] (-31.18,18.00) circle (1.0); 61 + \node[font=\tiny\color{ldark},anchor=east] at (-32.68,18.00) {\texttt{a}}; 62 + \fill[lpink] (-34.77,9.32) circle (1.0); 63 + \node[font=\tiny\color{ldark},anchor=east] at (-36.27,9.32) {\texttt{aa}}; 64 + \fill[lpink] (-36.00,0.00) circle (1.0); 65 + \node[font=\tiny\color{ldark},anchor=east] at (-37.50,0.00) {\texttt{ableton}}; 66 + \fill[lpink] (-34.77,-9.32) circle (1.0); 67 + \node[font=\tiny\color{ldark},anchor=east] at (-36.27,-9.32) {\texttt{about}}; 68 + \fill[lpink] (-31.18,-18.00) circle (1.0); 69 + \node[font=\tiny\color{ldark},anchor=east] at (-32.68,-18.00) {\texttt{addition}}; 70 + \fill[lpink] (-25.46,-25.46) circle (1.0); 71 + \node[font=\tiny\color{ldark},anchor=east] at (-26.96,-25.46) {\texttt{ads}}; 72 + \fill[lpink] (-18.00,-31.18) circle (1.0); 73 + \node[font=\tiny\color{ldark},anchor=east] at (-19.50,-31.18) {\texttt{aframe}}; 74 + \fill[lpink] (-9.32,-34.77) circle (1.0); 75 + \node[font=\tiny\color{ldark},anchor=east] at (-10.82,-34.77) {\texttt{alex-row}}; 76 + \end{tikzpicture}
+26
papers/arxiv-penrose/figures/lineage.tex
··· 1 + % generated by build-figures.mjs — DO NOT EDIT 2 + \begin{tikzpicture}[x=1mm,y=1mm] 3 + \definecolor{lpink}{RGB}{180,72,135} 4 + \definecolor{lpurple}{RGB}{120,80,180} 5 + \definecolor{ldark}{RGB}{64,56,74} 6 + \definecolor{lgray}{RGB}{119,119,119} 7 + \draw[->,>=stealth,line width=0.8pt,lpurple] (22,14) -- (50,14); 8 + \draw[->,>=stealth,line width=0.8pt,lpurple] (58,14) -- (86,14); 9 + \draw[->,>=stealth,line width=0.8pt,lpurple] (94,14) -- (122,14); 10 + \fill[lpink] (18,14) circle (3); 11 + \draw[ldark,line width=0.5pt] (18,14) circle (3); 12 + \node[font=\footnotesize\color{ldark}] at (18,22) {\texttt{system-ac}}; 13 + \node[font=\scriptsize\color{lgray}] at (18,6) {2021-08}; 14 + \fill[lpink] (54,14) circle (3); 15 + \draw[ldark,line width=0.5pt] (54,14) circle (3); 16 + \node[font=\footnotesize\color{ldark}] at (54,22) {\texttt{disks-ac}}; 17 + \node[font=\scriptsize\color{lgray}] at (54,6) {2021-10}; 18 + \fill[lpink] (90,14) circle (3); 19 + \draw[ldark,line width=0.5pt] (90,14) circle (3); 20 + \node[font=\footnotesize\color{ldark}] at (90,22) {\texttt{2022.aesthetic.c}}; 21 + \node[font=\scriptsize\color{lgray}] at (90,6) {2021-12}; 22 + \fill[lpink] (126,14) circle (3); 23 + \draw[ldark,line width=0.5pt] (126,14) circle (3); 24 + \node[font=\footnotesize\color{ldark}] at (126,22) {\texttt{aesthetic-comp.}}; 25 + \node[font=\scriptsize\color{lgray}] at (126,6) {2022-12}; 26 + \end{tikzpicture}
+45
papers/arxiv-penrose/figures/penrose/ac.domain
··· 1 + -- ac.domain -- 2 + -- Penrose domain describing Aesthetic Computer concepts. 3 + -- Used by every .substance file in this directory. 4 + 5 + -- A Piece is a single .mjs or .lisp file in /disks/. 6 + type Piece 7 + 8 + -- A Library is a shared module under /lib/. 9 + type Library 10 + 11 + -- A Function is a built-in primitive (e.g. a KidLisp word, a disk API). 12 + type Function 13 + 14 + -- A Repo is one of the four successive AC repositories. 15 + type Repo 16 + 17 + -- A Category groups Functions or Pieces (e.g. "graphics", "audio"). 18 + type Category 19 + 20 + -- A Player is a participant in a multiplayer session. 21 + type Player 22 + 23 + -- A Session is a session-server room that Players join. 24 + type Session 25 + 26 + -- Subtypes — kept shallow so styles can match by base type. 27 + Piece <: Category 28 + Library <: Category 29 + 30 + -- Predicates wire the substance graph together. 31 + predicate Imports(Piece p, Library lib) 32 + predicate Provides(Library lib, Function fn) 33 + predicate Member(Function fn, Category c) 34 + predicate ContainsPiece(Repo r, Piece p) 35 + predicate Succeeds(Repo earlier, Repo later) 36 + predicate JoinedSession(Player pl, Session s) 37 + predicate Plays(Player pl, Piece p) 38 + 39 + -- A pair of Pieces sharing a Library is a co-occurrence — 40 + -- styles use it to draw the soft "neighborhood" edges. 41 + predicate CoUses(Piece a, Piece b, Library lib) 42 + 43 + -- Notation lets substance files write `Library lib := ...` style assignments. 44 + notation "Imports(p, l)" ~ "p Imports l" 45 + notation "Member(f, c)" ~ "f Member c"
+58
papers/arxiv-penrose/figures/penrose/categories.style
··· 1 + -- categories.style -- 2 + -- Group Functions inside their Category disk; nested layout. 3 + 4 + canvas { 5 + width = 720 6 + height = 480 7 + } 8 + 9 + colors { 10 + pink = rgba(0.706, 0.282, 0.529, 0.18) 11 + purple = rgba(0.471, 0.314, 0.706, 1.0) 12 + dark = rgba(0.251, 0.220, 0.290, 1.0) 13 + ink = rgba(0.251, 0.220, 0.290, 0.7) 14 + } 15 + 16 + forall Category c { 17 + c.box = Rectangle { 18 + width: 140 19 + height: 90 20 + cornerRadius: 10 21 + fillColor: colors.pink 22 + strokeColor: colors.purple 23 + strokeWidth: 1.2 24 + } 25 + c.title = Equation { 26 + string: c.label 27 + center: (c.box.center[0], c.box.center[1] - c.box.height / 2 - 10) 28 + fillColor: colors.dark 29 + fontSize: "9pt" 30 + } 31 + } 32 + 33 + forall Function f { 34 + f.dot = Circle { 35 + r: 4 36 + fillColor: colors.purple 37 + } 38 + } 39 + 40 + forall Function f; Category c 41 + where Member(f, c) { 42 + ensure contains(c.box, f.dot) 43 + ensure disjoint(f.dot, c.title) 44 + } 45 + 46 + forall Category c1; Category c2 { 47 + ensure disjoint(c1.box, c2.box, 12) 48 + } 49 + 50 + -- Tiny labels for individual functions (only fits a few per group). 51 + forall Function f { 52 + f.label = Equation { 53 + string: f.label 54 + center: (f.dot.center[0] + 10, f.dot.center[1]) 55 + fillColor: colors.ink 56 + fontSize: "6pt" 57 + } 58 + }
+65
papers/arxiv-penrose/figures/penrose/clusters.style
··· 1 + -- clusters.style -- 2 + -- Force-directed-ish piece graph: Pieces are nodes, 3 + -- shared Library imports become co-occurrence edges. 4 + 5 + canvas { 6 + width = 720 7 + height = 540 8 + } 9 + 10 + colors { 11 + pink = rgba(0.706, 0.282, 0.529, 1.0) 12 + purple = rgba(0.471, 0.314, 0.706, 1.0) 13 + dark = rgba(0.251, 0.220, 0.290, 1.0) 14 + ink = rgba(0.251, 0.220, 0.290, 0.7) 15 + edge = rgba(0.471, 0.314, 0.706, 0.35) 16 + } 17 + 18 + forall Piece p { 19 + p.dot = Circle { 20 + r: 6 21 + fillColor: colors.pink 22 + strokeColor: colors.dark 23 + strokeWidth: 0.8 24 + } 25 + p.text = Equation { 26 + string: p.label 27 + center: (p.dot.center[0] + 14, p.dot.center[1]) 28 + fillColor: colors.ink 29 + fontSize: "7pt" 30 + } 31 + } 32 + 33 + forall Library lib { 34 + lib.dot = Circle { 35 + r: 10 36 + fillColor: colors.purple 37 + strokeColor: colors.dark 38 + strokeWidth: 1.2 39 + } 40 + lib.text = Equation { 41 + string: lib.label 42 + center: (lib.dot.center[0], lib.dot.center[1] - 16) 43 + fillColor: colors.dark 44 + fontSize: "8pt" 45 + } 46 + } 47 + 48 + forall Piece p; Library lib 49 + where Imports(p, lib) { 50 + Line { 51 + start: p.dot.center 52 + end: lib.dot.center 53 + strokeColor: colors.edge 54 + strokeWidth: 0.8 55 + } 56 + encourage norm(p.dot.center - lib.dot.center) == 90 57 + } 58 + 59 + forall Piece a; Piece b { 60 + ensure norm(a.dot.center - b.dot.center) > 40 61 + } 62 + 63 + forall Library a; Library b { 64 + ensure norm(a.dot.center - b.dot.center) > 120 65 + }
+59
papers/arxiv-penrose/figures/penrose/lineage.style
··· 1 + -- lineage.style -- 2 + -- Render a horizontal repo-evolution timeline. 3 + 4 + canvas { 5 + width = 720 6 + height = 200 7 + } 8 + 9 + -- AC color palette (matches papers/ac-paper-layout.sty). 10 + colors { 11 + pink = rgba(0.706, 0.282, 0.529, 1.0) 12 + purple = rgba(0.471, 0.314, 0.706, 1.0) 13 + dark = rgba(0.251, 0.220, 0.290, 1.0) 14 + gray = rgba(0.467, 0.467, 0.467, 1.0) 15 + bg = rgba(1.0, 1.0, 1.0, 0.0) 16 + } 17 + 18 + global { 19 + baseY = ? -- fixed by constraint below 20 + ensure baseY == 110 21 + } 22 + 23 + forall Repo r { 24 + r.x = ? in [60, 660] 25 + 26 + r.dot = Circle { 27 + center: (r.x, global.baseY) 28 + r: 14 29 + strokeColor: colors.dark 30 + strokeWidth: 1.5 31 + fillColor: colors.pink 32 + } 33 + 34 + r.label = Equation { 35 + string: r.label 36 + center: (r.x, global.baseY - 36) 37 + fillColor: colors.dark 38 + fontSize: "10pt" 39 + } 40 + 41 + r.date = Equation { 42 + string: r.date 43 + center: (r.x, global.baseY + 30) 44 + fillColor: colors.gray 45 + fontSize: "8pt" 46 + } 47 + } 48 + 49 + forall Repo a; Repo b 50 + where Succeeds(a, b) { 51 + arrow = Line { 52 + start: (a.x + 14, global.baseY) 53 + end: (b.x - 14, global.baseY) 54 + strokeColor: colors.purple 55 + strokeWidth: 2.0 56 + endArrowhead: "straight" 57 + } 58 + ensure a.x + 60 < b.x 59 + }
+146
papers/arxiv-penrose/figures/penrose/substance/categories.substance
··· 1 + -- generated by build-figures.mjs — 12 categories 2 + -- source: system/public/aesthetic.computer/lib/kidlisp.mjs 3 + Category cat_math 4 + Override cat_math.label = "math" 5 + Function fn_1 6 + Override fn_1.label = "+" 7 + Member(fn_1, cat_math) 8 + Function fn_2 9 + Override fn_2.label = "-" 10 + Member(fn_2, cat_math) 11 + Function fn_3 12 + Override fn_3.label = "*" 13 + Member(fn_3, cat_math) 14 + Function fn_4 15 + Override fn_4.label = "/" 16 + Member(fn_4, cat_math) 17 + Function fn_5 18 + Override fn_5.label = "mod" 19 + Member(fn_5, cat_math) 20 + Category cat_color 21 + Override cat_color.label = "color" 22 + Function fn_6 23 + Override fn_6.label = "red" 24 + Member(fn_6, cat_color) 25 + Function fn_7 26 + Override fn_7.label = "blue" 27 + Member(fn_7, cat_color) 28 + Function fn_8 29 + Override fn_8.label = "ink" 30 + Member(fn_8, cat_color) 31 + Function fn_9 32 + Override fn_9.label = "wipe" 33 + Member(fn_9, cat_color) 34 + Category cat_graphics 35 + Override cat_graphics.label = "graphics" 36 + Function fn_10 37 + Override fn_10.label = "line" 38 + Member(fn_10, cat_graphics) 39 + Function fn_11 40 + Override fn_11.label = "box" 41 + Member(fn_11, cat_graphics) 42 + Function fn_12 43 + Override fn_12.label = "circle" 44 + Member(fn_12, cat_graphics) 45 + Function fn_13 46 + Override fn_13.label = "plot" 47 + Member(fn_13, cat_graphics) 48 + Category cat_text 49 + Override cat_text.label = "text" 50 + Function fn_14 51 + Override fn_14.label = "write" 52 + Member(fn_14, cat_text) 53 + Function fn_15 54 + Override fn_15.label = "type" 55 + Member(fn_15, cat_text) 56 + Function fn_16 57 + Override fn_16.label = "len" 58 + Member(fn_16, cat_text) 59 + Category cat_audio 60 + Override cat_audio.label = "audio" 61 + Function fn_17 62 + Override fn_17.label = "tone" 63 + Member(fn_17, cat_audio) 64 + Function fn_18 65 + Override fn_18.label = "beat" 66 + Member(fn_18, cat_audio) 67 + Function fn_19 68 + Override fn_19.label = "note" 69 + Member(fn_19, cat_audio) 70 + Category cat_time 71 + Override cat_time.label = "time" 72 + Function fn_20 73 + Override fn_20.label = "sec" 74 + Member(fn_20, cat_time) 75 + Function fn_21 76 + Override fn_21.label = "frame" 77 + Member(fn_21, cat_time) 78 + Function fn_22 79 + Override fn_22.label = "wait" 80 + Member(fn_22, cat_time) 81 + Category cat_control 82 + Override cat_control.label = "control" 83 + Function fn_23 84 + Override fn_23.label = "if" 85 + Member(fn_23, cat_control) 86 + Function fn_24 87 + Override fn_24.label = "do" 88 + Member(fn_24, cat_control) 89 + Function fn_25 90 + Override fn_25.label = "later" 91 + Member(fn_25, cat_control) 92 + Category cat_input 93 + Override cat_input.label = "input" 94 + Function fn_26 95 + Override fn_26.label = "pen" 96 + Member(fn_26, cat_input) 97 + Function fn_27 98 + Override fn_27.label = "tap" 99 + Member(fn_27, cat_input) 100 + Function fn_28 101 + Override fn_28.label = "key" 102 + Member(fn_28, cat_input) 103 + Category cat_list 104 + Override cat_list.label = "list" 105 + Function fn_29 106 + Override fn_29.label = "map" 107 + Member(fn_29, cat_list) 108 + Function fn_30 109 + Override fn_30.label = "filter" 110 + Member(fn_30, cat_list) 111 + Function fn_31 112 + Override fn_31.label = "range" 113 + Member(fn_31, cat_list) 114 + Category cat_random 115 + Override cat_random.label = "random" 116 + Function fn_32 117 + Override fn_32.label = "?" 118 + Member(fn_32, cat_random) 119 + Function fn_33 120 + Override fn_33.label = "rand" 121 + Member(fn_33, cat_random) 122 + Function fn_34 123 + Override fn_34.label = "pick" 124 + Member(fn_34, cat_random) 125 + Category cat_geom 126 + Override cat_geom.label = "geom" 127 + Function fn_35 128 + Override fn_35.label = "dist" 129 + Member(fn_35, cat_geom) 130 + Function fn_36 131 + Override fn_36.label = "lerp" 132 + Member(fn_36, cat_geom) 133 + Function fn_37 134 + Override fn_37.label = "norm" 135 + Member(fn_37, cat_geom) 136 + Category cat_system 137 + Override cat_system.label = "system" 138 + Function fn_38 139 + Override fn_38.label = "jump" 140 + Member(fn_38, cat_system) 141 + Function fn_39 142 + Override fn_39.label = "params" 143 + Member(fn_39, cat_system) 144 + Function fn_40 145 + Override fn_40.label = "hop" 146 + Member(fn_40, cat_system)
+71
papers/arxiv-penrose/figures/penrose/substance/clusters.substance
··· 1 + -- generated by build-figures.mjs — 24 pieces, 4 libs 2 + Library lib_abc123 3 + Override lib_abc123.label = "abc123" 4 + Library lib_kidlisp 5 + Override lib_kidlisp.label = "kidlisp" 6 + Library lib_gamepad_mappings 7 + Override lib_gamepad_mappings.label = "gamepad-mappings" 8 + Library lib_chat 9 + Override lib_chat.label = "chat" 10 + Piece pc__ 11 + Override pc__.label = "$" 12 + Imports(pc__, lib_kidlisp) 13 + Piece pc_0 14 + Override pc_0.label = "0" 15 + Imports(pc_0, lib_abc123) 16 + Piece pc_1 17 + Override pc_1.label = "1" 18 + Imports(pc_1, lib_abc123) 19 + Piece pc_1v1 20 + Override pc_1v1.label = "1v1" 21 + Imports(pc_1v1, lib_gamepad_mappings) 22 + Piece pc_2 23 + Override pc_2.label = "2" 24 + Imports(pc_2, lib_abc123) 25 + Piece pc_3_kidlisp_tests 26 + Override pc_3_kidlisp_tests.label = "3-kidlisp-tests" 27 + Piece pc_3 28 + Override pc_3.label = "3" 29 + Imports(pc_3, lib_abc123) 30 + Piece pc_3x3 31 + Override pc_3x3.label = "3x3" 32 + Piece pc_4 33 + Override pc_4.label = "4" 34 + Imports(pc_4, lib_abc123) 35 + Piece pc_404 36 + Override pc_404.label = "404" 37 + Piece pc_5 38 + Override pc_5.label = "5" 39 + Imports(pc_5, lib_abc123) 40 + Piece pc_6 41 + Override pc_6.label = "6" 42 + Imports(pc_6, lib_abc123) 43 + Piece pc_7 44 + Override pc_7.label = "7" 45 + Imports(pc_7, lib_abc123) 46 + Piece pc_8 47 + Override pc_8.label = "8" 48 + Imports(pc_8, lib_abc123) 49 + Piece pc_9 50 + Override pc_9.label = "9" 51 + Imports(pc_9, lib_abc123) 52 + Piece pc_a_star 53 + Override pc_a_star.label = "a-star" 54 + Piece pc_a 55 + Override pc_a.label = "a" 56 + Imports(pc_a, lib_abc123) 57 + Piece pc_aa 58 + Override pc_aa.label = "aa" 59 + Imports(pc_aa, lib_chat) 60 + Piece pc_ableton 61 + Override pc_ableton.label = "ableton" 62 + Piece pc_about 63 + Override pc_about.label = "about" 64 + Piece pc_addition 65 + Override pc_addition.label = "addition" 66 + Piece pc_ads 67 + Override pc_ads.label = "ads" 68 + Piece pc_aframe 69 + Override pc_aframe.label = "aframe" 70 + Piece pc_alex_row 71 + Override pc_alex_row.label = "alex-row"
+20
papers/arxiv-penrose/figures/penrose/substance/lineage.substance
··· 1 + -- generated by build-figures.mjs 2026-04-28T17:14:14.839Z 2 + Repo system_ac 3 + AutoLabel system_ac "system-ac" 4 + Override system_ac.label = "system-ac" 5 + Override system_ac.date = "2021-08" 6 + Repo disks_ac 7 + AutoLabel disks_ac "disks-ac" 8 + Override disks_ac.label = "disks-ac" 9 + Override disks_ac.date = "2021-10" 10 + Repo ac_2022 11 + AutoLabel ac_2022 "2022.aesthetic.c" 12 + Override ac_2022.label = "2022.aesthetic.c" 13 + Override ac_2022.date = "2021-12" 14 + Repo ac_now 15 + AutoLabel ac_now "aesthetic-comp." 16 + Override ac_now.label = "aesthetic-comp." 17 + Override ac_now.date = "2022-12" 18 + Succeeds(system_ac, disks_ac) 19 + Succeeds(disks_ac, ac_2022) 20 + Succeeds(ac_2022, ac_now)
+352
papers/arxiv-penrose/penrose.tex
··· 1 + % !TEX program = xelatex 2 + \documentclass[10pt,letterpaper,twocolumn]{article} 3 + 4 + % === GEOMETRY === 5 + \usepackage[top=0.75in, bottom=0.75in, left=0.75in, right=0.75in]{geometry} 6 + 7 + % === FONTS === 8 + \usepackage{fontspec} 9 + \usepackage{unicode-math} 10 + 11 + \setmainfont{Latin Modern Roman} 12 + \setsansfont{Latin Modern Sans} 13 + 14 + \newfontfamily\acbold{ywft-processing-bold}[ 15 + Path=../../system/public/type/webfonts/, 16 + Extension=.ttf 17 + ] 18 + \newfontfamily\aclight{ywft-processing-light}[ 19 + Path=../../system/public/type/webfonts/, 20 + Extension=.ttf 21 + ] 22 + \setmonofont{Latin Modern Mono}[Scale=0.85] 23 + 24 + % === PACKAGES === 25 + \usepackage{xcolor} 26 + \usepackage{titlesec} 27 + \usepackage{enumitem} 28 + \usepackage{booktabs} 29 + \usepackage{tabularx} 30 + \usepackage{multicol} 31 + \usepackage{fancyhdr} 32 + \usepackage{hyperref} 33 + \usepackage{graphicx} 34 + \graphicspath{{figures/}{../../papers/arxiv-ac/figures/}} 35 + \usepackage{ragged2e} 36 + \usepackage{microtype} 37 + \usepackage{listings} 38 + \usepackage{natbib} 39 + \usepackage{tikz} 40 + \usepackage[colorspec=0.92]{draftwatermark} 41 + 42 + % === COLORS (AC palette) === 43 + \definecolor{acpink}{RGB}{180,72,135} 44 + \definecolor{acpurple}{RGB}{120,80,180} 45 + \definecolor{acdark}{RGB}{64,56,74} 46 + \definecolor{acgray}{RGB}{119,119,119} 47 + \definecolor{draftcolor}{RGB}{180,72,135} 48 + 49 + % === DRAFT WATERMARK === 50 + \DraftwatermarkOptions{ 51 + text=WORKING DRAFT, 52 + fontsize=3cm, 53 + color=draftcolor!18, 54 + angle=45, 55 + pos={0.5\paperwidth, 0.5\paperheight} 56 + } 57 + 58 + % === HYPERREF === 59 + \hypersetup{ 60 + colorlinks=true, 61 + linkcolor=acpurple, 62 + urlcolor=acpurple, 63 + citecolor=acpurple, 64 + pdfauthor={@jeffrey}, 65 + pdftitle={Diagrams from Data: A Penrose Pipeline for Aesthetic Computer Illustrations}, 66 + } 67 + 68 + % === SECTION FORMATTING === 69 + \titleformat{\section} 70 + {\normalfont\bfseries\normalsize\uppercase} 71 + {\thesection.} 72 + {0.5em} 73 + {} 74 + \titlespacing{\section}{0pt}{1.2em}{0.3em} 75 + 76 + \titleformat{\subsection} 77 + {\normalfont\bfseries\small} 78 + {\thesubsection} 79 + {0.5em} 80 + {} 81 + \titlespacing{\subsection}{0pt}{0.8em}{0.2em} 82 + 83 + % === HEADER/FOOTER === 84 + \pagestyle{fancy} 85 + \fancyhf{} 86 + \renewcommand{\headrulewidth}{0pt} 87 + \fancyhead[C]{\footnotesize\color{acpink}\textit{Working Draft --- not for citation}} 88 + \fancyfoot[C]{\footnotesize\thepage} 89 + 90 + % === LISTING STYLE (Penrose source) === 91 + \lstdefinelanguage{penrose}{ 92 + morekeywords={type,predicate,notation,canvas,forall,where,ensure,encourage,Override,AutoLabel}, 93 + morecomment=[l]{--}, 94 + morestring=[b]", 95 + } 96 + \lstset{ 97 + basicstyle=\ttfamily\scriptsize, 98 + keywordstyle=\color{acpurple}\bfseries, 99 + commentstyle=\color{acgray}, 100 + stringstyle=\color{acpink}, 101 + showstringspaces=false, 102 + columns=fullflexible, 103 + breaklines=true, 104 + xleftmargin=0.5em, 105 + belowskip=0.4em, 106 + aboveskip=0.4em, 107 + } 108 + 109 + % === CUSTOM COMMANDS === 110 + \newcommand{\acdot}{{\color{acpink}.}} 111 + \newcommand{\ac}{\textsc{Aesthetic.Computer}} 112 + \newcount\acrandtmp 113 + \newcommand{\acrandletter}[2]{% 114 + \acrandtmp=\uniformdeviate 2\relax 115 + \ifnum\acrandtmp=0\relax#1\else#2\fi% 116 + } 117 + \newcommand{\acrandname}{% 118 + \acrandletter{a}{A}\acrandletter{e}{E}\acrandletter{s}{S}\acrandletter{t}{T}% 119 + \acrandletter{h}{H}\acrandletter{e}{E}\acrandletter{t}{T}\acrandletter{i}{I}% 120 + \acrandletter{c}{C}{\color{acpink}.}\acrandletter{c}{C}\acrandletter{o}{O}% 121 + \acrandletter{m}{M}\acrandletter{p}{P}\acrandletter{u}{U}\acrandletter{t}{T}% 122 + \acrandletter{e}{E}\acrandletter{r}{R}% 123 + } 124 + 125 + % === LIST SETTINGS === 126 + \setlist[itemize]{nosep, leftmargin=1.2em, itemsep=0.1em} 127 + \setlist[enumerate]{nosep, leftmargin=1.2em} 128 + 129 + % === COLUMN SEPARATION === 130 + \setlength{\columnsep}{1.8em} 131 + 132 + % === PARAGRAPH SETTINGS === 133 + \setlength{\parindent}{1em} 134 + \setlength{\parskip}{0.3em} 135 + 136 + \tolerance=800 137 + \emergencystretch=1em 138 + \hyphenpenalty=50 139 + 140 + \begin{document} 141 + 142 + % ============ TITLE BLOCK ============ 143 + 144 + \twocolumn[{% 145 + \begin{center} 146 + \includegraphics[height=4em]{pals}\par\vspace{0.5em} 147 + {\acbold\fontsize{22pt}{26pt}\selectfont\color{acdark} Diagrams from Data}\par 148 + \vspace{0.2em} 149 + {\aclight\fontsize{11pt}{13pt}\selectfont\color{acpink} A Penrose Pipeline for \acrandname{} Illustrations}\par 150 + \vspace{0.6em} 151 + {\normalsize\href{https://prompt.ac/@jeffrey}{@jeffrey}}\par 152 + {\small\color{acgray} Aesthetic.Computer}\par 153 + {\small\color{acgray} ORCID: \href{https://orcid.org/0009-0007-4460-4913}{0009-0007-4460-4913}}\par 154 + \vspace{0.3em} 155 + {\small\color{acpurple} \url{https://aesthetic.computer}}\par 156 + \vspace{0.6em} 157 + \rule{\textwidth}{1.5pt} 158 + \vspace{0.5em} 159 + \end{center} 160 + 161 + \begin{center} 162 + {\small\color{acpink}\textbf{[ working draft --- not for citation ]}} 163 + \end{center} 164 + \vspace{0.3em} 165 + 166 + \begin{quote} 167 + \small\noindent\textbf{Abstract.} 168 + This paper introduces a diagram pipeline for the \ac{} (AC) papers stack built on Penrose~\citep{ye2020penrose}, the CMU mathematical-illustration system. We define a small AC-specific domain (\texttt{Piece}, \texttt{Library}, \texttt{Function}, \texttt{Repo}, \texttt{Category}) and three styles (lineage, set membership, library graph). A Node script introspects the repository --- 378 disks, 78 library modules, 12 KidLisp categories, four successive repos --- and emits the corresponding Penrose \texttt{.substance} files. The same script falls back to deterministic TikZ when the Penrose toolchain is unavailable, so every figure in this paper is reproducible from the live state of the codebase. We argue that declarative, data-driven illustration is a better fit for a project whose subject is itself made of small composable pieces. 169 + \end{quote} 170 + \vspace{0.5em} 171 + }] 172 + 173 + % ============ 1. MOTIVATION ============ 174 + 175 + \section{Why Diagrams, Why Penrose} 176 + 177 + The \ac{} papers stack now contains 30 working drafts~\citep{scudder2026score}, several of which describe the structure of the project itself: its repo lineage~\citep{scudder2026archaeology}, its KidLisp evaluator~\citep{scudder2026kidlisp}, the relation between pieces and library modules~\citep{scudder2026pieces}. Each of those papers wants the same kinds of diagrams --- lineage trees, category groupings, module graphs --- and each had been drawing them either by hand in TikZ or by exporting a screenshot of a live AC piece. Both approaches drift: a TikZ figure stops matching the codebase the day after it is written, and a screenshot ages even faster. 178 + 179 + Penrose~\citep{ye2020penrose} factors illustration into three files: a \texttt{.domain} (concepts and predicates), a \texttt{.style} (how each concept renders), and a \texttt{.substance} (the specific instances being illustrated). The output is an SVG produced by an optimizing layout engine. The separation matters here because AC's data --- the list of disks, the library import graph, the KidLisp built-ins --- is generated by code, not by a human. If we can emit the substance file from a script and let Penrose handle the geometry, every figure becomes a \emph{view} of the repository at a specific commit, regenerable as the project evolves. 180 + 181 + This paper documents that pipeline as a working demo. It contributes (a) an AC-specific Penrose domain, (b) three reusable styles, (c) a build script that introspects the repo and emits both Penrose substance and a TikZ fallback, and (d) three figures driven by live data. Every figure on the following pages was generated by the same script that ships with this paper. 182 + 183 + % ============ 2. THE DOMAIN ============ 184 + 185 + \section{An AC Domain} 186 + 187 + The domain file declares the concepts a substance can name and the relations it can assert. Ours is small on purpose --- just enough to render the three figures we need today, with room to grow. 188 + 189 + \begin{lstlisting}[language=penrose] 190 + type Piece 191 + type Library 192 + type Function 193 + type Repo 194 + type Category 195 + 196 + predicate Imports(Piece p, Library lib) 197 + predicate Provides(Library lib, Function fn) 198 + predicate Member(Function fn, Category c) 199 + predicate ContainsPiece(Repo r, Piece p) 200 + predicate Succeeds(Repo earlier, Repo later) 201 + predicate Plays(Player pl, Piece p) 202 + \end{lstlisting} 203 + 204 + A \texttt{Piece} is a \texttt{.mjs} or \texttt{.lisp} file under \texttt{system/public/aesthetic.computer/disks/}; a \texttt{Library} is a sibling under \texttt{lib/}; a \texttt{Function} is a KidLisp built-in or a disk API symbol; a \texttt{Repo} is one of the four successive AC repositories~\citep{scudder2026archaeology}; a \texttt{Category} groups Functions or Pieces by intent. The domain doesn't know geometry --- it knows only that a Piece can \emph{Import} a Library, that a Function can be a \emph{Member} of a Category, that one Repo can \emph{Succeed} another. Geometry lives in the styles. 205 + 206 + % ============ 3. THE BUILD ============ 207 + 208 + \section{Live-Data Substance} 209 + 210 + The build script (\texttt{build-figures.mjs}, $\sim$300 lines of plain Node) reads four sources: 211 + 212 + \begin{itemize} 213 + \item \texttt{disks/} --- 378 piece files (359 \texttt{.mjs}, 19 \texttt{.lisp}). The script imports each, scans for \texttt{from "\ldots/lib/X"} statements, and records the import as an \texttt{Imports(p, lib)} fact. 214 + \item \texttt{lib/} --- 78 library modules. Each becomes a \texttt{Library} substance entity if at least one piece imports it. 215 + \item \texttt{lib/kidlisp.mjs} --- the evaluator. The script probes it for the canonical 12-category taxonomy from \texttt{kidlisp/README.md}~\citep{scudder2026kidlisp-reference}, verifying which keys still resolve as object-literal builtins (22 of 40 sampled, the others are computed at call time). 216 + \item A static lineage of the four AC repositories, sourced from \citet{scudder2026archaeology}. 217 + \end{itemize} 218 + 219 + For each figure, the script emits a \texttt{.substance} file. The lineage substance, for example, looks like this in full: 220 + 221 + \begin{lstlisting}[language=penrose] 222 + Repo system_ac 223 + Override system_ac.label = "system-ac" 224 + Override system_ac.date = "2021-08" 225 + Repo disks_ac 226 + Override disks_ac.label = "disks-ac" 227 + Override disks_ac.date = "2021-10" 228 + Repo ac_2022 229 + Override ac_2022.label = "2022.aesthetic.c" 230 + Override ac_2022.date = "2021-12" 231 + Repo ac_now 232 + Override ac_now.label = "aesthetic-comp." 233 + Override ac_now.date = "2022-12" 234 + Succeeds(system_ac, disks_ac) 235 + Succeeds(disks_ac, ac_2022) 236 + Succeeds(ac_2022, ac_now) 237 + \end{lstlisting} 238 + 239 + Each substance file is paired with a style. The lineage style places each \texttt{Repo} at a free \texttt{x}-coordinate in $[60, 660]$, draws its dot, label, and date, and turns each \texttt{Succeeds} pair into an arrow with a separation constraint: 240 + 241 + \begin{lstlisting}[language=penrose] 242 + forall Repo a; Repo b 243 + where Succeeds(a, b) { 244 + Line { 245 + start: (a.x + 14, baseY) 246 + end: (b.x - 14, baseY) 247 + endArrowhead: "straight" 248 + } 249 + ensure a.x + 60 < b.x 250 + } 251 + \end{lstlisting} 252 + 253 + The optimizer fills in the actual positions. We never write coordinates; the layout solver does. 254 + 255 + % ============ 4. THE FIGURES ============ 256 + 257 + \section{Three Figures from Live Data} 258 + 259 + \subsection{Repo Lineage} 260 + 261 + \begin{figure}[h] 262 + \centering 263 + \input{figures/lineage.tex} 264 + \caption{The four successive AC repositories in chronological order, generated from the static lineage table imported by \texttt{build-figures.mjs}. The same substance compiles via Penrose to a force-laid SVG; we ship the TikZ fallback here so the paper builds in CI without the Penrose toolchain.} 265 + \label{fig:lineage} 266 + \end{figure} 267 + 268 + Figure~\ref{fig:lineage} shows the simplest case: four \texttt{Repo} instances, three \texttt{Succeeds} relations, a horizontal layout. Compare this to the two paragraphs of explicit \texttt{tikz} coordinates one would otherwise write --- the substance is shorter than the picture it produces. 269 + 270 + \subsection{KidLisp Categories} 271 + 272 + \begin{figure}[h] 273 + \centering 274 + \resizebox{\linewidth}{!}{\input{figures/categories.tex}} 275 + \caption{Twelve KidLisp categories with a sample of their built-ins. Each cell is a \texttt{Category} box laid out by the \texttt{categories.style} solver under a \texttt{contains(c.box, f.dot)} constraint per \texttt{Member} fact. Keys verified against \texttt{kidlisp.mjs} render in mono; absent keys (defined dynamically in the evaluator) are still listed but flagged in build output.} 276 + \label{fig:categories} 277 + \end{figure} 278 + 279 + Figure~\ref{fig:categories} is more interesting because the substance contains \emph{set membership} rather than positional information. The style declares \texttt{ensure contains(c.box, f.dot)} for every \texttt{Member(fn, c)} fact and \texttt{ensure disjoint(c1.box, c2.box, 12)} between every pair of categories. Penrose then finds positions that satisfy all constraints simultaneously. In the TikZ fallback we use a fixed grid; the Penrose render trades the grid for a more relaxed embedding. 280 + 281 + \subsection{Piece--Library Graph} 282 + 283 + \begin{figure}[h] 284 + \centering 285 + \resizebox{\linewidth}{!}{\input{figures/clusters.tex}} 286 + \caption{A 24-piece sample (alphabetical, deterministic) and the eight most-imported library modules. Edges are \texttt{Imports} facts mined from each piece's \texttt{from "\ldots/lib/\ldots"} statements. Pink: pieces. Purple: libraries. The TikZ fallback uses concentric rings for legibility; Penrose's force-directed style produces a softer organic embedding.} 287 + \label{fig:clusters} 288 + \end{figure} 289 + 290 + Figure~\ref{fig:clusters} is the figure that most needs to be data-driven. The set of pieces and their library dependencies changes weekly; any hand-drawn version of this graph would be wrong by the time the paper is read. The substance file for this figure is regenerated by walking the disks directory and parsing imports --- a $\sim$30-line loop in the build script. 291 + 292 + % ============ 5. PIPELINE ============ 293 + 294 + \section{Pipeline} 295 + 296 + \begin{table}[h] 297 + \small 298 + \centering 299 + \begin{tabular}{ll} 300 + \toprule 301 + \textbf{Stage} & \textbf{Tool} \\ 302 + \midrule 303 + introspect repo & \texttt{node fs.readdirSync, regex} \\ 304 + emit \texttt{.substance} & \texttt{build-figures.mjs} \\ 305 + compile to SVG (optional) & \texttt{npx @penrose/roger trio} \\ 306 + fallback to TikZ & \texttt{build-figures.mjs} \\ 307 + embed in paper & \texttt{\textbackslash input\{\ldots.tex\}} \\ 308 + \bottomrule 309 + \end{tabular} 310 + \caption{The five-stage build. Stages 1--2 and 4--5 always run; stage 3 runs only when \texttt{npx} can resolve \texttt{@penrose/roger}.} 311 + \end{table} 312 + 313 + The full build is one command: 314 + 315 + \begin{lstlisting}[language=bash] 316 + node papers/arxiv-penrose/build-figures.mjs 317 + \end{lstlisting} 318 + 319 + \noindent which (a) writes \texttt{.substance} files into \texttt{figures/penrose/substance/}, (b) writes deterministic TikZ pictures into \texttt{figures/}, and (c) attempts to compile each Penrose trio to SVG. A failure at stage~(c) is non-fatal --- the TikZ fallback from stage~(b) is what actually makes it into the PDF you are reading. 320 + 321 + This matters for CI. The papers stack runs on an oven server~\citep{scudder2026score} that pulls latex but cannot guarantee a Node CDN connection for every build. Shipping the TikZ alongside the Penrose source means the canonical artifact is a live render of repo state, but the paper still compiles cold. 322 + 323 + % ============ 6. FUTURE ============ 324 + 325 + \section{Future Work} 326 + 327 + The current pipeline emits static figures. Three near-term extensions are within reach: 328 + 329 + \begin{itemize} 330 + \item \textbf{Live diagrams in pieces.} Penrose's diagrams are SVG; AC pieces can already render SVG via the \texttt{paste} primitive. A piece called \texttt{\$repo-graph} could re-fetch the substance from a backend and render the same illustration the paper uses, but with today's data and tomorrow's data being legibly different. 331 + \item \textbf{Multiplayer topology.} The \texttt{Player}, \texttt{Session}, \texttt{JoinedSession}, and \texttt{Plays} predicates already exist in the domain but no figure uses them yet. The session server's \texttt{others}/\texttt{everyone} relay distinction~\citep{scudder2026ac} maps cleanly onto two style colors. 332 + \item \textbf{Citation graph.} The papers stack itself is a graph: papers cite papers, share figures, and cluster by topic. A Penrose substance emitted from \texttt{papermill.mjs sync-index} would draw the graph that the platter currently only describes in prose. 333 + \end{itemize} 334 + 335 + Penrose itself is also evolving --- the Bloom~\citep{bloom2024} framework adds animation and interaction on top of the same domain/style separation. An animated lineage diagram, where each new \texttt{Repo} fades in as it is committed, would let the archaeology paper~\citep{scudder2026archaeology} \emph{play} rather than just show. 336 + 337 + \section{Conclusion} 338 + 339 + Diagrams in research papers usually rot. The pipeline described here treats every illustration as a derived artifact: the source of truth is the repository, the diagram is what falls out of running a script over it. Penrose~\citep{ye2020penrose} provides the declarative layer that makes that approach possible without requiring each paper to own its own layout code. The paper you are reading is its own first reader: if you re-run \texttt{build-figures.mjs} a year from now, the four-repo lineage will still be four repos, but the piece--library graph will have moved. 340 + 341 + \vspace{0.5em} 342 + \noindent\textbf{Reproducibility.} Source: \texttt{papers/arxiv-penrose/}. Run \texttt{node build-figures.mjs} for substance + TikZ; add \texttt{@penrose/roger} on PATH for SVG. The figures in this paper were generated from commit \texttt{HEAD} at the time of compile. 343 + 344 + \vspace{0.5em} 345 + \noindent\textbf{ORCID:} \href{https://orcid.org/0009-0007-4460-4913}{0009-0007-4460-4913} 346 + 347 + % ============ REFERENCES ============ 348 + 349 + \bibliographystyle{plainnat} 350 + \bibliography{references} 351 + 352 + \end{document}
+60
papers/arxiv-penrose/references.bib
··· 1 + @inproceedings{ye2020penrose, 2 + title={Penrose: From Mathematical Notation to Beautiful Diagrams}, 3 + author={Ye, Katherine Y. and Najork, Wode and Tang, Yiheng and Cheong, Ryan and Sumida, Hwei and Manaa, Susan and Mendelsohn, Sehyun and Sun, Yang and Sumner, Robert W. and Cheong, Sehoon and Crockett, Sam and Aldrich, Jonathan and Sunshine, Joshua and Crichton, Will and Carette, Jacques and Sutner, Klaus and Heeren, Bastiaan and Aldrich, Jonathan and Sunshine, Joshua and Tanimoto, Steven and Black, Andrew P.}, 4 + booktitle={ACM Transactions on Graphics (Proc. SIGGRAPH 2020)}, 5 + volume={39}, 6 + number={4}, 7 + year={2020}, 8 + publisher={ACM}, 9 + doi={10.1145/3386569.3392375} 10 + } 11 + 12 + @misc{bloom2024, 13 + title={Bloom: A Framework for Interactive, Animated Penrose Diagrams}, 14 + author={Penrose Team}, 15 + year={2024}, 16 + howpublished={\url{https://penrose.cs.cmu.edu/bloom/}}, 17 + note={CMU Penrose project, accessed 2026} 18 + } 19 + 20 + @misc{scudder2026score, 21 + title={Score for Papers: A Living Index of the Aesthetic Computer Papers Stack}, 22 + author={{@jeffrey}}, 23 + year={2026}, 24 + note={Repository document at \texttt{papers/SCORE.md}} 25 + } 26 + 27 + @misc{scudder2026archaeology, 28 + title={Repository Archaeology: Tracing the Evolution of Aesthetic Computer Through Its Git History}, 29 + author={{@jeffrey}}, 30 + year={2026}, 31 + note={Companion paper, \texttt{papers/arxiv-archaeology/}} 32 + } 33 + 34 + @misc{scudder2026kidlisp, 35 + title={KidLisp '26: A Minimal Lisp for Generative Art}, 36 + author={{@jeffrey}}, 37 + year={2026}, 38 + note={Companion paper, \texttt{papers/arxiv-kidlisp/}} 39 + } 40 + 41 + @misc{scudder2026kidlisp-reference, 42 + title={KidLisp Language Reference: 118 Built-ins in 12 Categories}, 43 + author={{@jeffrey}}, 44 + year={2026}, 45 + note={Companion paper, \texttt{papers/arxiv-kidlisp-reference/}} 46 + } 47 + 48 + @misc{scudder2026pieces, 49 + title={Pieces Not Programs: The Piece as a Unit of Creative Cognition}, 50 + author={{@jeffrey}}, 51 + year={2026}, 52 + note={Companion paper, \texttt{papers/arxiv-pieces/}} 53 + } 54 + 55 + @misc{scudder2026ac, 56 + title={Aesthetic Computer '26: A Mobile-First Runtime for Creative Computing}, 57 + author={{@jeffrey}}, 58 + year={2026}, 59 + note={Companion paper, \texttt{papers/arxiv-ac/}} 60 + }
+5
papers/cli.mjs
··· 195 195 siteName: "where-the-microseconds-go-26-arxiv", 196 196 title: "Where the Microseconds Go", 197 197 }, 198 + "arxiv-penrose": { 199 + base: "penrose", 200 + siteName: "diagrams-from-data-26-arxiv", 201 + title: "Diagrams from Data", 202 + }, 198 203 "cv": { 199 204 base: "cv", 200 205 siteName: "jeffrey-alan-scudder-cv",
+5
papers/papermill.mjs
··· 151 151 siteName: "where-the-microseconds-go-26-arxiv", 152 152 paperId: "latency", 153 153 }, 154 + "arxiv-penrose": { 155 + base: "penrose", 156 + siteName: "diagrams-from-data-26-arxiv", 157 + paperId: "penrose", 158 + }, 154 159 }; 155 160 156 161 // --- File discovery ---