Monorepo for Aesthetic.Computer aesthetic.computer
README.md

Portable No Paint brushes #

An executable architecture experiment: one brush source, read and written by JavaScript and pure Swift. The name remains brush. Its execution resembles a kernel: explicit input pixels, parameters, a recorded gesture, seed and logical time produce a new pixel buffer, without IO or changes to the accepted painting.

Line and Invert use canonical sources in fixtures/*.brush.json. There is no Swift Line or Swift Invert to maintain beside a JavaScript version. legacy-line and wander remain regression fixtures for old painting files. Each runtime implements the small instruction set, not individual brushes. An editor or generator writes the same structured source; a future compiler must preserve its semantics. These are reference interpreters, not a GPU compiler or a replacement for the existing AC brush catalog.

Run and verify #

From the repository root:

bash toolchain/macos/swift-guard.sh build --package-path experiments/nopaint-brushes --jobs 2
node experiments/nopaint-brushes/conformance.mjs

The Swift executable reads {document, preview?} JSON on stdin and emits {document, pixels}. BrushCore has no JavaScript interpreter or UI dependency. The JS module can be imported directly in a browser. The AppKit prototype in Sources/BrushApp imports the Swift library and displays its RGBA output, with the painting above an equal-width No/Paint decision pair. The painting itself is the third target: click it for Back/Done (save). Brush selection, undo/redo and files live in native menus; time and source editing are in Brush Source. A 60 Hz logical frame loop grows each preview. No discards and immediately restarts; Paint bakes the exact current invocation and immediately restarts. Accepted pixels survive both transitions. Its bundled brushes are copied from the same canonical fixtures at build time.

Build and open the native window on this host:

BRUSH_SWIFT_SDK=/Library/Developer/CommandLineTools/SDKs/MacOSX26.5.sdk \
  bash experiments/nopaint-brushes/build-app.sh
open 'experiments/nopaint-brushes/.build/No Paint Brushes.app'

Both workbenches start with a square 256×256 painting. The native window opens with a compact 320×400-point content area (resizable down to a 280×350-point content area). The viewport fills the square edge to edge, preserving its aspect ratio and showing the entire painting. The title is No Paint — Brush Name.

The local app is ad-hoc signed, not a notarized release. The SDK override works around the toolchain mismatch described below; omit it on a matching toolchain.

The browser workbench previews Line and Invert, edits and saves brush JSON, accepts/rejects proposals, and saves/reopens painting documents with undo/redo:

python3 -m http.server 8765 --bind 127.0.0.1 --directory experiments/nopaint-brushes
# Open http://127.0.0.1:8765/

Conformance compares every byte across three brushes, varying time and seed, alpha, path directions, clipped stamps, wrapped drift, rejection, acceptance, undo/redo, invalid programs and work exhaustion. Swift decodes and re-encodes the document; JS reopens it and renders it again. Independent numeric assertions check alpha blending, a diagonal, and double inversion. The contact sheet at .build/qa/parity.ppm puts JS above Swift: Line, Wander, Invert.

Validation on the development host: 163 cross-runtime cases passed. Browser checks passed for preview/accept, undo/redo, editing source, reject, and preserving the canvas after invalid source. Both the parity sheet and browser screenshot were inspected. These are prototype checks, not production AC integration tests.

The host's SwiftPM fails before compilation with a BuildServerProtocol dynamic library mismatch. The installed compiler also defaults to an incompatible 27.0 SDK. Conformance was verified by compiling directly with the installed matching 26.5 SDK, without changing host configuration:

cd experiments/nopaint-brushes
mkdir -p .build/debug
nice -n 8 swiftc -sdk /Library/Developer/CommandLineTools/SDKs/MacOSX26.5.sdk \
  -emit-library -emit-module -module-name BrushCore Sources/BrushCore/Brush.swift \
  -emit-module-path .build/debug/BrushCore.swiftmodule -o .build/debug/libBrushCore.dylib
nice -n 8 swiftc -sdk /Library/Developer/CommandLineTools/SDKs/MacOSX26.5.sdk \
  -I .build/debug -L .build/debug -lBrushCore -Xlinker -rpath -Xlinker @executable_path \
  Sources/BrushRunner/main.swift -o .build/debug/brush-runner
node conformance.mjs

Source contract, experimental version 1 #

A brush is {schema: "ac-brush", version: 1, id, operations}. Operations run in array order. The source is JSON, an explicit syntax tree rather than executable JavaScript or Swift. JSON whitespace and object-key order carry no meaning. Only the documented fields form the portable contract; arbitrary extension metadata is not covered by the round-trip guarantee.

An invocation embeds the complete brush plus an unsigned 32-bit seed and integer tick. There is no dependency on a mutable remote catalog entry. Ticks are logical 1/60-second steps supplied by the host, never wall-clock time. The same invocation always starts from the same input canvas, including previews.

Instruction Inputs Effect
path integer points, RGBA color, radius, durationTicks, jitter, drift Reveal a path over time; draw integer disk stamps along Bresenham segments
walk steps, RGBA color, radius, durationTicks, jitter, drift Generate an inertial seeded path, then render it progressively
invert none Invert RGB of the current working buffer, retaining alpha

Coordinates address pixel centers from the top left, x rightward and y downward. Storage is row-major, straight RGBA8. Blending operates on encoded byte values, not linear light. RGB source-over uses A = sa*255 + da*(255-sa) and round((src*sa*255 + dst*da*(255-sa))/A); output alpha is round(A/255). Zero total alpha leaves the destination unchanged. Rounding is positive integer half-up. Invert also inverts RGB beneath zero alpha. No native drawing API participates in these reference pixels.

The Line source now uses walk with 128 points and 1800 ticks (30 seconds). Each No/Paint restart gets a new seed; an accepted invocation retains its seed. The gesture stops at its bounded duration until another decision is made. walk starts with x=random(width), y=random(height) and velocities random(7)-3 for x then y. For each subsequent point, add random(3)-1 to both velocities (x then y), clamping each to -3…3. If both are zero, set vx=1. Reverse a velocity if its next position would cross that canvas edge; advance and clamp coordinates to the canvas. Then run the same jitter/drift and path renderer as path. The source records a rule rather than fixed coordinates. Steps are bounded to 2–128. Tests cover seed variation, exact replay, continued motion, and tiny canvases in both implementations.

Random state advances as (state * 1664525 + 1013904223) mod 2^32. Every path point consumes x then y samples, even for zero jitter. A sample modulo 2*jitter+1, minus jitter, gives its offset. Add tick * drift and wrap each center into the canvas before rendering. Disk edges clip at the canvas. Path progress is (pointCount-1) * min(tick,durationTicks) / durationTicks. Render completed segments and the current partial segment, interpolating its endpoint with integer half-up rounding. This advances within a segment each frame instead of revealing whole segments at once. The first point is stamped once. Each Bresenham segment skips its starting point and includes its endpoint, using the original doubled error for both axis tests. Overlapping stamps blend repeatedly; this is deliberate prototype behavior, not a claim of equivalence to AC Line's existing rasterizer.

Version 1 bounds: dimensions 1–256, 1–32 instructions, 1–128 path points, coordinates 0–255, radius/jitter 0–16, drift -8–8, duration 1–3600, tick 0–3600. Each invocation permits two million work units: one per disk-square sample (even clipped samples) or inverted pixel. Exceeding the budget fails without returning a partial result. Programs are validated before execution. A document contains at most 64 invocations. These small bounds are for experimentation, not production resolution targets or protection for a public HTTP endpoint.

Painting and controller boundary #

The self-contained prototype document has schema ac-painting-prototype, version 1, dimensions, base RGBA bytes, steps (invocations) and cursor. The cursor selects the accepted history prefix. A preview is separate from the document. Reject drops the preview; accept appends its exact invocation after truncating any redo branch. Undo/redo moves the cursor. Rendering never performs a commit. Hosts own those controller actions; the test harness exercises their pixel effects.

This is a compatibility fixture, not a migration of nopaint-piece storage. Production documents should pin brush source, assets and engine semantics by version/content digest and retain raster checkpoints. Existing JS-only artwork must retain its pixels when source cannot execute in Swift. Replay must not silently substitute today's brush for an older version.

Placement in Aesthetic Computer #

The existing design in papers/nopaint-3-full-shape/ already calls for one painting substrate, a conductor, and a reducer owning explicit acceptance. Preserve that separation. Do not introduce a second authoritative canvas just to support generated brushes or a native app.

Responsibility Existing seam Proposed evolution
Accepted painting and history lib/nopaint-pieces.mjs, AC shared painting One document/revision boundary; AC raster edits become explicit checkpoints
Brush behavior nopaintProposal exports, lib/nopaint-brush-piece.mjs Portable brush source and runtime adapter where behavior is representable
Proposal decisions disks/nopaint.mjs Controller owns preview, reject, accept and history navigation
Gestures, viewport, HUD systems/nopaint.mjs, piece handlers Host adapters turn input into explicit brush inputs
Save, recording and handoff recording and WIP helpers Effects of accepted document revisions; neither renderer saves
Brush supply Static catalogs Versioned registry for authored and generated sources
Native application New Swift host Same brush sources/documents through BrushCore, native UI and files

Current brushes often combine interaction, parameter generation and rendering. Migration should separate these responsibilities, preserving their artistic behavior. A recorded path can become explicit points, but this prototype does not yet express pressure, persistent per-brush state, arbitrary sampling, sprites, audio or general mathematical expressions. Wander exercises seeded animation; it is not a recovered Construct brush.

The production representation needs those representative cases before choosing JSON as the final authoring language. A textual DSL or a constrained KidLisp subset could compile to the structured representation, but only if both implementations agree on numeric behavior and editing preserves the source. Avoid maintaining two independently authored versions of each brush. GPU backends can come later, with declared fidelity and the same conformance suite.

Brushery #

Brushery supplies new brush source to the registry. A worker on jastow or poorslice can collect public themes, generate candidates, validate their capabilities, render examples on both backends and promote passing versions. The system endpoint serves immutable artifacts and a catalog. It needs no AC handle. Catalog publication does not accept a proposal into anyone's painting.

The model budget is $5 per day across generation, critique and repair. Before each call, reserve its maximum possible cost in a durable ledger; reconcile actual usage afterward. Use a day key, one active job lease and bounded retries so restarts or a second worker cannot spend the budget twice. Model output cannot access publication credentials. Keep chat/news as source material, never instructions. Pin evidence with candidates; use aggregate growth figures. No daemon, paid model calls or publication endpoint is enabled by this prototype.

Next gates: representative artistic fidelity beyond the prototype; one AC adapter and broader native interaction testing; document migration and checkpoint policy; then unattended generation and catalog promotion. The current pixel proof is deliberately separate from those production decisions.

Native interaction: all three targets have hover/press feedback. Holding a No/Paint button freezes logical time. Moving across No hides the proposal; Paint reveals the same frozen proposal. Releasing over a destination chooses that destination, and releasing outside cancels and resumes. Holding the painting pauses; a valid release toggles completion. The 14 existing AC LEGACY_CUES are extracted at build time by prepare-audio.mjs, decoded to WAV using installed headless Chrome, and played with NSSound. Pressed crossings use the destination press cue; passive hover uses rollover once per entry. The native host does not yet import brush-theme loops or background music. For local interaction checks, BRUSH_UI_TRACE can name an existing JSONL file; only logical ticks, seeds, accepted counts and interaction states are recorded.

Each native No/Paint decision now chooses a different one of the three implemented brushes and samples explicit color, opacity, radius, duration, geometry, jitter/drift or inversion strength. The accepted invocation embeds those exact values, so replay needs no sampling. Invert optionally accepts amount (0–255) and durationTicks: effective strength is half-up rounded amount * min(tick,duration) / duration; RGB blends original and inverse with that byte weight. Omitted amount is 255, and omitted duration preserves the original immediate inversion behavior.

Pause by clicking the painting, then right-click for About Brush. The window explains its behavior and shows the actual parameters, seed and source. The same inspector is available from Brush → About Brush (Command-I).

Account → Sign In to AC opens the existing AC browser pairing flow. Only kind: browser is requested: this receives an AC identity/session, not native provisioning credentials. The polling secret stays out of the browser URL. Session storage uses the macOS Keychain; Sign Out removes only this app's session. Cancel stops pending requests before they can save credentials. This flow supplies an access token without a refresh token; sign in again when it expires. Painting saves remain local; cloud sync/publishing is not enabled. The production pairing route responded as expected to a request missing its code. Authentication logic was tested with mocked transport/storage (pending, claim, persistence, sign-out, cancellation, expiry and malformed responses). An actual account sign-in still requires the person to complete the browser flow.

Run account checks with the matching SDK on this host:

nice -n 8 swiftc -sdk /Library/Developer/CommandLineTools/SDKs/MacOSX26.5.sdk \
  experiments/nopaint-brushes/Sources/BrushApp/ACAccount.swift \
  experiments/nopaint-brushes/tests/account.swift \
  -o experiments/nopaint-brushes/.build/account-check
experiments/nopaint-brushes/.build/account-check

Separate gesture input #

Line uses {"op":"stroke","radius":1,"color":[255,60,140,180]}. Each invocation carries gesture: [{x,y,tick}, ...]: 1–128 samples with integer canvas coordinates (0–255), strictly increasing ticks (0–3600), at 60 ticks/sec. Before the first sample it paints nothing. Between samples it reveals a timed prefix of the complete Bresenham segment, so earlier pixels never drift as a segment grows. Color/opacity use the existing per-stamp compositing semantics. Radius is 0–16 (diameter 1–33 pixels). Pressure, tilt and pen-up events are not part of this first stream format yet.

The native host generates Wander, Sweep, Loop or Zigzag in Gesture.swift, then saves the actual samples with the invocation. These generators are input sources, not brushes: replay needs neither their code nor their random seed. The Gesture menu selects one or samples automatically. Brush → Thickness and Opacity replay the same gesture with new settings, as does applying edited brush source. No/Paint chooses fresh input and parameters. Invert ignores the stream. Legacy path/walk operations remain readable for saved prototypes.

The browser workbench currently supplies a simple diagonal stream; it can reopen native documents and replay their complete recorded gestures. Tests cover timed prefixes, seed independence, style variation, input validation and JS/Swift replay/round-trips for the new stroke operation.

Native Invert previews the full color inversion immediately (amount 255, no duration). It does not randomize its strength. Paint accepts; No discards. Gesture generator checks cover all four modes, three canvas sizes, and three seeds (36 cases).

Install the built app in ~/Applications/No Paint.app and pin it to the Dock:

bash experiments/nopaint-brushes/install-app.sh

The installer verifies signing, preserves other Dock items, and avoids duplicate pins. It keeps the original Dock preferences under ~/Library/Application Support/No Paint/dock-before-install.plist. It also accepts a prebuilt app bundle path for another compatible Mac.