# Pixelbolt

To **use** Pixelbolt for artwork, start with [the pixel art skill](skills/pixel-art/SKILL.md) and [the quick start](docs/agent-quick-start.md), then [the authoring guide](docs/agent-guide.md) and [schema.json](schema.json) as needed. [llms.txt](llms.txt) indexes the public documentation; [README.md](README.md) includes CLI and MCP setup. The instructions below apply when **contributing to the toolkit**.

This is an agent-first pixel art toolkit. Keep the core and runtime free of dependencies. Use standard Node.js APIs for the CLI, PNG/APNG encoding, ZIP and the local server. No build step is required.

- `src/core.js`: browser-compatible recipe validation/rasterization/atlas packing, inspection (contact sheets, palette-key grids) and revision comparison.
- `src/craft.js`: browser-compatible pixel-art helpers shared by the renderer, patches and compilers: ordered, noise and value-noise dither, De-Corner/De-Stray cleanup, RotSprite-style rotation, `turnRows` (a picture turned as a pixel artist turns a sprite: sides turned to whole pixel steps, outline and frame kept closed; its guard test in `test/craft.test.js` holds every whole degree), `draftTurn` (a small grid part turned as a draft view: exact quarter turns, then whole columns slid along a clean slope for a long or thin part or an area-keeping silhouette coloured by outline ring for a compact one; `test/draft-views.test.js` holds it to the hero's hand-drawn boot views, and its golden hashes are in `test/goldens/drafts.json`), rewrite rules, easing, seeded hashing and engine-independent trigonometry. Keep new randomness seeded and new maths polynomial so output stays identical across JavaScript engines.
- `src/font.js`: browser-compatible built-in pixel font (printable ASCII, western European accented letters, common symbols) and text layout for the `text` drawing operation. Glyph changes change rendered pixels, so treat them as format changes: regenerate examples and the goldens (`node scripts/goldens.mjs`) and review.
- `src/fx.js`: browser-compatible `pixelforge-fx` compiler that bakes seeded particle emitters into ordinary recipes.
- `src/shapes.js`: browser-compatible shape layers for `pixelforge-fx` (ring, slash, beam, bolt, glyph, star, burst, stream): intensity fields in frame windows, plus the fx compiler's blob particles, cut into palette bands by named looks with lit and shade bands from the field's fall toward a light, then cleanup and outline, baked into one grid per frame. Bands hold from frame to frame (a look's `hold`), so frames are drawn in order with one memory, and a loop is read once to settle the bands its seam holds. Same determinism rules as `craft.js` (its own polynomial `atan2Deg`); the golden hashes in `test/goldens/shapes.json` change with any change to its output, so regenerate them deliberately (`node scripts/goldens.mjs shapes`, then `--write`).
- `src/wave.js`: browser-compatible `pixelforge-wave` compiler for looping coherent radial/directional fields. Sum signed sources before palette mapping; use craft trigonometry and fixed arithmetic. Bands hold at thresholds as a look's do (`look.hold`), settled round the loop. `scripts/wave-presets.mjs` supplies the reviewed examples; `showcase/waves/build.mjs --check` and the golden hashes in `test/goldens/wave.json` (`node scripts/goldens.mjs wave`) protect their pixels. This is an art tool, not an obstacle or water solver.
- `src/crystal.js`: browser-compatible `pixelforge-crystal` compiler: crystals that grow from seeds and come to rest. Faceted habits (six-sided prisms with pointed, flat or double ends, cubes, octahedra) are small convex solids turned by angle (snapped to clean pixel slopes), tilt and turn, seen straight on, each visible face one band of its look by how squarely it faces the one light, outlined, grown by easing their size about the seed (a finished crystal is painted once and held), alone or in seeded clusters spread along a line of points; snowflakes grow tip by tip in one quadrant mirrored into the other three (two-to-one slanted arms, side branches confined to their arm's wedge and stopping a pixel short of anything else). Each crystal seeds as one bright pixel and glints along its lit ridge once grown. Same determinism rules as `craft.js`; `scripts/crystal-presets.mjs` supplies the reviewed examples (`examples/*.crystal.json`), and the golden hashes in `test/goldens/crystal.json` and the GIFs from `showcase/crystals/build.mjs` change with any change to its output, so regenerate both deliberately (`node scripts/goldens.mjs crystal`, then `--write`).
- `src/fluid.js`: browser-compatible `pixelforge-fluid` compiler: stable-fluids gases and FLIP/APIC liquids on a one-cell-per-pixel grid, with implicit stress-form viscosity, yield stress, adhesion and granular friction (sand: friction from pressure, rigid pieces at rest), and rigid bodies (bodies.js) as moving solids that stir what they turn in, that water floats and drags, what rests on them weighs on and resting sand holds up, and that gases push, in loops, with heat and in the live runner (`fluidForces`, `groundBodies`, `stepBodies`), baked into palette `grid` operations. Same determinism rules as `craft.js` (only + − × ÷ and sqrt, seeded hashing, fixed steps and iterations); the golden hashes in `test/goldens/fluid.json` and the GIFs from `showcase/fluid/build.mjs` change with any change to its output, so regenerate both deliberately (`node scripts/goldens.mjs fluid`, then `--write`). New fluid physics (and any module it adds) follows [docs/physics-model.md](docs/physics-model.md): looks and satisfying motion first, then one set of units, one heat scale, one solid layer, one notion of air, one step order and a matter ledger, so features written apart still agree.
- `src/electric.js`: browser-compatible electricity and plasma for `pixelforge-fluid` gases: the field solve (conjugate gradients over a conductance network of side and corner links), dielectric-breakdown growth, plasma as linked markers that ride the gas, electrode supplies (resistance, charge) and dart restrikes down a channel's trace. `src/fluid.js` only calls it from small hooks (reading, the gas step, the look fields). Same determinism rules as `craft.js` (only + − × ÷ and sqrt, seeded draws through `random`, fixed iteration caps, cells, markers and links visited in a fixed order); the golden hashes of its presets (`scripts/plasma-presets.mjs`, written to `examples/`) in `test/goldens/fluid.json` and their GIFs change with any change to its output, so regenerate them deliberately (`node scripts/goldens.mjs fluid`, then `--write`).
- `src/bodies.js`: browser-compatible `pixelforge-bodies` compiler: rigid circles, boxes, convex polygons and fixed (or moving) hoops with gravity, walls and pixel obstacles traced into one-sided runs of exposed pixel edges, solved by sequential impulses in substeps (pairs found on a grid of 4-pixel cells, speculative contacts, warm starting, a relaxed push out of overlaps, restitution after the positions move), with sleeping, `asleep` bodies held until hit, the open `tireless` cheat, fixed bodies turned about a `pivot` and swing `smear`s, bodies that break into convex Voronoi pieces carrying their part of the picture when knocked hard enough (`breaks`, or the `break` power), baked into palette `grid` operations; boxes, polygons and symbols drawn turned to the whole degree by craft.js `turnRows` (under 6 pixels, square), or with a source's `subpixels` finer than the world's pixels by `paintBodiesFine` (each body's picture turned exactly and placed to a fraction of a pixel, the world drawn in blocks), and a drawn place steps on as a body passes halfway the way it last moved but back only once it is clearly past halfway. `createBodyWorld` steps it a frame at a time for a fluid, and `bodiesFrames` a match frame after frame for `src/sequence.js` (up to 18000 frames, never a recipe). Same units as the fluid (docs/physics-model.md, section 2) and the same determinism rules as `craft.js`; the golden hashes in `test/goldens/bodies.json` (presets in `scripts/bodies-presets.mjs`, written to `examples/`) and the GIFs from `showcase/bodies/build.mjs` change with any change to its output, so regenerate both deliberately (`node scripts/goldens.mjs bodies`, then `--write`).
- `src/territory.js`: browser-compatible territory, power-ups and heads-up display for `pixelforge-bodies` (docs/physics-model.md, section 13a): cells owned by teams, traced per team into walls with bodies.js's face tracer, a cell a body hits changing hands at the end of the substep, held speeds, pickups, events and strikes (a power for whatever a named body pushes, or with `cut` passes through and leaves behind it: a blade that splits every ball it touches, a crowd of up to `most` bodies) with split, grow, speed, warp, magnet and vanish, at the end of a frame, and the score drawn as recipe `text` and `rect` operations. `src/bodies.js` only calls it from small hooks (reading, contacts, the end of a substep and of a frame, drawing). Same determinism rules as `craft.js`; the territory preset's golden hash in `test/goldens/bodies.json` and its GIF change with any change to its output.
- `src/patch.js`: browser-compatible targeted recipe edits and canvas-coordinate painting addressed by error-style paths.
- `src/autotile.js`: browser-compatible blob/cardinal/corner neighbour masks (and `{corners}` names for layered sets), template quarter layout and tilemap legends (variants, weights, neighbour rules), shared by scene tilemaps, the recipe `tilemap` operation and the autotile compiler in `src/authoring.js`. It imports `craft.js`, so copies of it (Tidewatch's game) need both.
- `src/terrain.js`: browser-compatible autotile templates drawn from a `terrain` description (a fill that repeats every tile, an edge per side with bands, margins and tufts, rounded corners), the seam check that compares every quarter join a map can make with the template's own continuation, and the test map holding all 47 tiles. A terrain is seamless by construction: `test/terrain.test.js` stitches its tiles into maps and compares them with the terrain drawn directly, and its golden hashes in `test/goldens/terrain.json` for `scripts/terrain-presets.mjs` (the reviewed `examples/*.autotile.json`) change with any change to what a terrain draws, so regenerate them deliberately (`node scripts/goldens.mjs terrain`, then `--write`). Same determinism rules as `craft.js`.
- `src/slopes.js`: browser-compatible slopes for side-view blob and cardinal sets (`slopes` in an autotile source): 45° and gentle (a cell up over two) tiles, each column the flat top edge moved down the slope over the fill in place, a foot for the cell under a slope's low end, collision under each slope's line, and the map legend and hill map the compiler hands out (the test map draws them). Whole-pixel arithmetic only.
- `src/pose-geometry.js`: deterministic joint angles and integer two-bone reach; `src/motions.js`: rig/template expansion into editable poses, with `rig.tail` following through on damped springs driven by the body's motion (stepped in fixed substeps with craft trigonometry, a loop settled round its seam); `src/motion.js`: advisory contact, slide, ground, pose-spread and loop review with bounded images.
- `src/authoring.js`: browser-compatible compilers whose output is an ordinary recipe: poses, autotile templates and normal passes derived from a colour recipe (`compileNormals`; deterministic, no trigonometry). `draftViews` drafts grid parts' `views` (`compile --draft-views`, MCP `pixel_compile` `draftViews`) as source for an author to correct and paste; it compiles nothing and never writes a source, and the compiler only ever draws authored views.
- `src/resolve.js`: Node-side resolution of shared palette files, scene asset references and a normals source's recipe; the renderer itself never reads files.
- `src/revisions.js`: immutable on-disk MCP recipe snapshots under the configured output directory.
- `src/export.js`, `src/png.js`: deterministic file exports.
- `src/gif.js`: browser-compatible GIF89a export for sharing, used by the CLI, MCP and studio. Exact colours only: it never quantizes or dithers, and reports what GIF cannot keep (partial alpha, sub-10 ms timing) instead of hiding it. `framesGIF` writes frames made elsewhere at a frame rate, with a colour table per frame when the loop needs more than 256 colours.
- `src/godot.js`: browser-compatible Godot 4 `SpriteFrames`, `CanvasTexture` (a lit scene asset's colour pass with its normal pass flipped to +Y up) and, for tile sets (autotile frames found by `autotileFrames` in `src/autotile.js`, and frames with a `tile` field: weight, collision, terrain), `TileSet` resources (`.tres`) written from atlas metadata, one atlas source per sheet and one terrain set per terrain, laid out as Godot 4.5+ saves them; the bundle, MCP `saveAs` and scene exports (one TileSet over every tile-set asset) use it. `node scripts/verify-godot.mjs` loads exports in a real headless Godot 4 and checks regions, margins, pixels, timing and playback, probabilities and collision polygons, and paints maps with Godot's terrain painter, which must pick the tiles Pixelbolt's tilemap does (three sets on one layer for a scene's TileSet), and checks a lit asset's CanvasTexture (`--render` also lights it in a Godot window, which needs a desktop); run it after changing a `.tres`, the atlas, normal passes or autotile masks.
- `src/tiled.js`: browser-compatible Tiled tilesets (`.tsx`) written from atlas metadata, one per sheet: the untrimmed sheet as Tiled's grid, each terrain a Wang set (blob mixed, cardinal edge, corner and layered sets corner) whose Wang ids are the tiles' masks, and each tile's probability, collision objects, `oneWay` property and animation; the bundle, MCP `saveAs` (`tiled`) and scene exports (one per asset) use it. It also writes a scene's `scene.tmx` (`tiledMap`) from what `renderScene` drew at the start (`draws: true`): tile layers for the tilemaps Tiled's grid holds, tile objects in drawing order for the rest. `node scripts/verify-tiled.mjs` loads them in a real headless Tiled (1.10.2 or later), checks what Tiled reads, paints the Godot check's maps with Tiled's own terrain filler, which must pick the tiles Pixelbolt's tilemap does, and renders every tile with `tmxrasterizer`, then renders a scene's `scene.tmx`, which must draw the unlit scene; run it after changing a `.tsx`, `.tmx`, the atlas, autotile masks or scene drawing.
- `src/frame-folder.js`: Node-side reading of a folder of numbered PNG frames for `gif-frames`: number order, gaps, other files and mixed sequences are reported, not guessed. It reads the folder and never writes to it.
- `src/sequence.js`: numbered PNG frames for video editors, from recipes, scenes and bodies matches (stepped frame after frame, so past 256 frames): exact frame-rate fractions, whole-number enlargement onto a video-sized canvas (contained, or covering it and cropped), a sidecar and the ffmpeg command for an alpha video. It encodes no video itself; keep it that way.
- `src/mcp.js`, `src/server.js`: agent and studio interfaces. MCP tool calls run one at a time in a worker thread (`src/mcp-worker.js`, `createToolRunner`), so a call past the time or memory limit, or one the client cancels, is stopped without stopping the server; only `ping` and cancellations skip the queue. Keep tool code free of state between calls other than files on disk.
- `src/help.js`: Node-side help topics shared by MCP `pixel_help` and `pixelbolt help <topic> [section]`: the quick start, the guides with their `## ` sections listed (one section by name), the schemas and the example.
- `src/check.js`: the `check` in `pixel_render` and `render` replies: what an export delivered (frame size, frames, loops, colours, transparency, GIF size) against the user's `expect`, each mismatch with the call that fixes it, and each animation's silhouette boxes with advisory notes on named loops whose solid body lifts off its ground row, steps unevenly or jumps at the loop, or that repeat a frame; the notes never fail the check. Read-only.
- `src/presets.js`: Node-side presets: the shipped `examples/` sources (`*.fluid.json`, `*.fx.json`, `*.wave.json`, `*.crystal.json`, `*.bodies.json`, `*.poses.json`) and looping plain recipes an agent compiles instead of drawing, for `pixel_help` topics `presets` and `preset`, `pixel_compile`'s `preset`, `pixelbolt presets` and `compile --preset`. It reads the folder when asked and takes titles from the Fluid and Effects labs' lists, so a new example is listed with no list to update; a preset compiles exactly as its file does. The listing an agent reads by default is a line per preset (`briefListing`), under a size cap in `test/presets.test.js`; `full` gives every field. The MCPB bundle ships every top-level example for it.
- `src/runtime.js`: exported Canvas animation player.
- `src/share.js`: browser-compatible share links (the recipe in a playground URL's fragment) and recipes inside PNGs (a compressed iTXt chunk, keyword `pixelbolt-recipe`). It has no static imports, so the playground serves a byte-identical copy as `playground/share.js`; `test/share.test.js` keeps the two equal.
- `src/skill.js`: Node-side `pixelbolt skill install`: the pixel art skill on its own in a skills folder (`~/.claude/skills` by default), its commands rewritten to `npx -y pixelbolt@<version>` and the guides, schemas, examples and licence copied beside it, with a SHA-256 manifest so a changed copy is never replaced without `--force`. It never changes `skills/pixel-art/SKILL.md`, which the package and the plugin ship as written; when that skill cites a new guide, add it to `DOCS`.
- `studio/`: plain HTML/CSS/JS preview studio; it shares the core renderer. `studio/fluid.html` and `fluid-lab.js` (the Fluid lab) compile fluid presets in the browser with `src/fluid.js`; `studio/effects.html` and `effects-lab.js` (the Effects lab) do the same for the self-contained `pixelforge-fx` presets listed in `effects-controls.js`, which the server's allowlist reads. `studio/draft.js` keeps each tab's unsaved recipe in the browser's own storage, under an id the tab keeps in `sessionStorage`, and never touches another open tab's; draft recovery must stay in the browser, never a server endpoint.
- `playground/`: the browser playground for the project site, not in the npm package. Static files: it loads the engine from jsDelivr at the version pinned in `playground/app.js`, renders in a module worker with a time limit, and every PNG it exports carries its recipe. `playground/render.js` takes the engine as an argument, so Node renders exactly what the page renders. `node scripts/deploy-playground.mjs <gh-pages checkout>` prepares the site change (see `playground/DEPLOY.md`); it never pushes. `playground/fluids/` is the live fluid toy: `createLiveFluid` driven by the pointer, GIFs of the last seconds, and links that replay a visitor's pokes frame for frame; `fluids/lib.js` holds its presets and link format, and its `TOY_VERSION` stays equal to the playground's pin.
- `packaging/`: launcher scripts, START HERE and notices for the portable builds: `common/` for every system (START HERE.txt is one text with `#if windows|macos|linux` blocks), `windows/` batch files, `unix/` shell scripts for macOS and Linux, `macos/` double-click `.command` files. `scripts/build-studio.mjs` packs them with the `npm pack` file list and the pinned official Node.js runtime into a reproducible archive per target under `dist/`; `scripts/verify-studio.mjs` tests a built archive on its own system.
- `scripts/qa-studio.mjs`: walks the studio in an installed Edge or Chrome, headless with a throwaway profile, over the DevTools protocol with Node's own WebSocket. No browser-automation dependency; run it after studio changes. Packaging adds nothing to `package.json`'s dependencies or `files`, and its scripts only print or serve: they never edit a user's configuration.
- `scripts/goldens.mjs`: the one command for pinned output. Golden hashes live in `test/goldens/<set>.json`, one set per test file, and each case stays defined in its test, checked with `golden.equal` inside `golden.test` (the `goldens()` helper this script exports). `node scripts/goldens.mjs [set | set/case ...]` runs only the golden tests, lists every case whose output changed and draws its frames before (the last commit, or `--base <ref>`) and after side by side under `output/goldens/`; look at them, then `--write` updates the hashes. `test/fixtures/figures-baseline.json` is the frozen pre-M0 baseline, not a golden set: never rewrite it.
- `scripts/related-tests.mjs` (`npm run test:related -- <files or folders>`; with none, the files changed since the last commit): runs only the test files that import them, directly or through other modules, found from a static import graph. The graph is file-level and some modules (`src/authoring.js`, `src/mcp.js`) are imported widely, so a change to a shared module still reaches most tests.
- `schema.json`, `poses.schema.json`, `scene.schema.json`, `autotile.schema.json`, `fx.schema.json`, `fluid.schema.json`, `wave.schema.json`, `crystal.schema.json`, `bodies.schema.json`, `normals.schema.json`: generated with `node scripts/generate-schema.js`.
- `examples/`: generated with `node scripts/generate-examples.js`.

Run `npm test` after functional changes; while working, `npm run test:related -- <files>` is the quicker loop, and `npm run goldens` shows which pinned outputs moved. Tests and the studio walk never assert how long something takes, since a busy CI runner would fail them with nothing broken: hold the work instead (a count such as `renderProject`'s `stats`), and give a speed target a `budget` case in `scripts/bench.mjs`, which CI reports without failing. Keep the schema, authoring reference and runtime validation aligned. Keep what an agent reads on connect small: MCP tool schemas describe a recipe and each option briefly (the server validates them, and the guide topic explains every option), `pixel_help` returns the short quick start, and longer references are its topics; a test caps both. Binary export changes should also be checked with an independent image decoder. Do not replace the text-first workflow with a UI-only drawing tool.

Never silently overwrite source recipes or generated assets. MCP renders create unique folders. The preview server must stay on loopback with an explicit asset allowlist and no arbitrary file-writing endpoints.

## Commit and push workflow

The repository owner authorizes agents working on this project to commit completed changes and push directly to `main` by default. After reviewing the diff and passing the relevant checks (including `npm test` for functional changes), commit and push without asking for routine confirmation or opening a pull request. This is the standing workflow for this small project unless the user requests a different approach.

Before committing, inspect existing uncommitted changes and preserve them. Include changes from other agents when the user has authorized combining that work; otherwise leave unrelated work untouched. Fetch before pushing and integrate upstream changes without discarding local work. Never force-push, rewrite published history, reset away changes or delete branches as part of this authorization. Report the resulting commit and push status; if pushing fails, explain the concrete blocker.
