From 799caa12dffc2f3869b89ef3ae672abd5f719fc4 Mon Sep 17 00:00:00 2001 From: "prompt.ac/@jeffrey" Date: Tue, 15 Sep 2026 13:08:05 -0700 Subject: [PATCH] Start No Paint home visits fresh and keep lines inside the canvas --- system/public/aesthetic.computer/boot.mjs | 3 +- .../public/aesthetic.computer/disks/line.mjs | 14 +++++- system/tests/nopaint-line-bounds.test.mjs | 43 +++++++++++++++++++ 3 files changed, 57 insertions(+), 3 deletions(-) create mode 100644 system/tests/nopaint-line-bounds.test.mjs diff --git a/system/public/aesthetic.computer/boot.mjs b/system/public/aesthetic.computer/boot.mjs index 192c76a771..82e10f4b3c 100644 --- a/system/public/aesthetic.computer/boot.mjs +++ b/system/public/aesthetic.computer/boot.mjs @@ -1145,7 +1145,8 @@ if ( location.hostname === "nopaint.art" || location.hostname === "www.nopaint.art" ) { - window.acSTARTING_PIECE = "nopaint"; + // The front door starts a new canvas; rewritten session URLs resume. + window.acSTARTING_PIECE = location.pathname === "/" ? "nopaint~fresh" : "nopaint"; } if ( location.hostname === "oskiewar.com" || diff --git a/system/public/aesthetic.computer/disks/line.mjs b/system/public/aesthetic.computer/disks/line.mjs index 9f493dec50..5c1feb2ecf 100644 --- a/system/public/aesthetic.computer/disks/line.mjs +++ b/system/public/aesthetic.computer/disks/line.mjs @@ -284,7 +284,17 @@ const nopaintProposal = Object.freeze({ Math.round(start.y + (end.y - start.y) * t - sway * 0.65))), }); }); - const thickness = 1 + Math.floor(random() * 50); + const shortSide = Math.min(width, height); + const maxThickness = Math.max(1, Math.min(50, Math.floor(shortSide * 0.12))); + const thickness = 1 + Math.floor(random() * maxThickness); + // Keep the whole stroke visible, including rounded caps and a little + // breathing room. Scale the path inward so it keeps its shape at edges. + const inset = Math.min(Math.floor((shortSide - 1) / 2), + Math.ceil(thickness / 2) + Math.max(2, Math.round(shortSide * 0.04))); + const insetPoints = points.map(({ x, y }) => Object.freeze({ + x: Math.round(inset + x / Math.max(1, width - 1) * (width - 1 - inset * 2)), + y: Math.round(inset + y / Math.max(1, height - 1) * (height - 1 - inset * 2)), + })); const alpha = 24 + Math.floor(random() * 169); const color = Object.freeze([base.color[0], base.color[1], base.color[2], alpha]); const durationFrames = 24 + Math.floor(random() * 97); @@ -292,7 +302,7 @@ const nopaintProposal = Object.freeze({ ...base, color, thickness, - points: Object.freeze(points), + points: Object.freeze(insetPoints), brush: Object.freeze({ slug: "line", params: Object.freeze(color.map(String)), diff --git a/system/tests/nopaint-line-bounds.test.mjs b/system/tests/nopaint-line-bounds.test.mjs new file mode 100644 index 0000000000..751eb8755b --- /dev/null +++ b/system/tests/nopaint-line-bounds.test.mjs @@ -0,0 +1,43 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { nopaintProposal } from "../public/aesthetic.computer/disks/line.mjs"; +import { makeProposal, seededRandom } from "../public/aesthetic.computer/lib/nopaint-proposals.mjs"; +import { color, line, setBuffer } from "../public/aesthetic.computer/lib/graph.mjs"; + +test("Line's entire raster stays inside the painting with a visible edge margin", () => { + const guard = 32; + for (const [width, height] of [[8, 8], [96, 32], [256, 256], [390, 676], [844, 312], [1200, 700]]) { + const surface = { + width: width + guard * 2, + height: height + guard * 2, + pixels: new Uint8ClampedArray((width + guard * 2) * (height + guard * 2) * 4), + }; + for (let seed = 0; seed < 16; seed++) { + const random = seededRandom(seed); + const base = makeProposal(random, width, height); + // Include paths that reach both opposite corners before insetting. + const edgeBase = seed % 2 === 0 + ? { ...base, x: 0, y: 0, w: width - 1, h: height - 1 } + : base; + const score = nopaintProposal.generate({ random, width, height, base: edgeBase }); + surface.pixels.fill(0); + setBuffer(surface); + nopaintProposal.render({ ink: (rgba) => { + color(...rgba); + return { line: (x0, y0, x1, y1, thickness) => + line(x0 + guard, y0 + guard, x1 + guard, y1 + guard, thickness) }; + } }, score, 600); + let count = 0; + for (let index = 3; index < surface.pixels.length; index += 4) { + if (!surface.pixels[index]) continue; + count++; + const pixel = (index - 3) / 4; + const x = pixel % surface.width - guard; + const y = Math.floor(pixel / surface.width) - guard; + assert.ok(x > 0 && x < width - 1 && y > 0 && y < height - 1, + `${width}x${height}, seed ${seed}: stroke pixel ${x},${y} touches or crosses an edge`); + } + assert.ok(count > 0, "the proposal remains visible"); + } + } +}); -- 2.51.2