Repository navigation
Integrate dperegolise/pixelsrc fork: validator, extends, particles bake, outline, show - #11
Merged
Merged
Conversation
The midpoint-walk implementation produced angular discs (a r=7 circle read as a flattened hexagon) and its region-2 loop could step x past the radius, making the widest rows one pixel too wide (16px for a 15px-diameter circle). Replaced with a per-row span fill using the conventional pixel-art inside test ((dx/(rx+.5))^2+(dy/(ry+.5))^2 <= 1), clamped to the radius. Golden tests pin exact per-row spans for r=2..16 (span, no holes, no overshoot) and the r=7 silhouette (narrow pole, monotone widths, full-diameter equator). Existing ellipse tests and the analyze-side shape detector (which compares against this function) stay green.
…ns, unused tokens Four silent wrong-output paths become warnings (errors under --strict): - Duplicate region key: JSON5 deserialization keeps only the last value, so the earlier region vanishes with no signal — the #1 practical footgun. Caught by a string/comment/depth-aware scan of the raw sprite text, since it is unrecoverable after parse. - Out-of-canvas pixels: rasterized regions whose pixels the renderer would silently clip are reported with a count and an example coordinate. Validation rasterizes exactly the way the renderer does (same two-pass fill/auto-shadow order). - Empty regions: zero-pixel regions (e.g. the unimplemented auto-outline) are reported, with a pointed suggestion for the auto-outline case. - Unused palette tokens: the mirror-image symptom of a misspelled region key. Inline palettes are checked per sprite; named palettes are checked at end of file across all local sprites, and palette-only library files stay quiet (their users may live in other files). Unit tests cover each, plus the nested-objects-not-confused case for the raw scanner and the palette-only-file silence.
pxl diff compared only dimensions and palettes: two same-size, same-palette sprites with completely different pixels printed 'No differences found', and a sprite missing from one file did too (the removed/added summary was built but format_diff's is_empty() check ignored it). For a verification tool, a silent pass is worse than no tool. SpriteDiff gains region_changes and presence_change, both honored by is_empty(), text output, and --json. Regions are rasterized the way the renderer rasterizes them and compared per token: added, removed, shifted (exact (dx,dy) when the shape translated as a unit — walk-cycle bobs and frame tears show up as exactly this), or changed with the differing-pixel count. Tests: shift detection, shape change, added/removed tokens, and a presence diff that must never print 'No differences'.
Animation frame-sets share most of their regions and differ in only a few — yet today every frame is a full copy, which is verbose and the source of frame-tear bugs (one copy drifts a pixel and the sprite rips). The live case: hearthfire's four distill frames quadruplicate an identical stone ring around a flame that is all that actually moves. `extends: "base"` resolves the base sprite's region map, then patches it: each key in this sprite's `regions` replaces the inherited region of that name (or adds a new one), and `remove: [..]` deletes inherited keys. An override carries its own z/role, so it is free to re-stack itself without disturbing the other regions. The base palette and size are inherited; `palette` may be omitted on an extending sprite (it is still required on every other sprite — the validator enforces that) and, when present, overrides inherited tokens key-by-key like a variant. Chains resolve base-first. This is distinct from `source`, which copies the rendered image and can only apply whole-image transforms — `extends` merges at the region level. Resolution lives in SpriteRegistry; the single-sprite render path and the `mask` inspector both resolve inheritance so `pxl render`/`pxl mask` show the merged sprite. Validator: omitted-palette-without-extends and remove-without-extends are flagged; `missing_palette` moves from a parse error to a validation error (an omitted palette now parses). Docs: region-inheritance section in format/sprite.md with the extends-vs-source table. Tests: registry resolution (override/add/remove, chain, missing-base, remove-missing), a render golden, a mask-resolution test, and two validator cases. Suite: lib 1857 passed / 1 pre-existing unrelated fail (install::tests::test_install_skips_cached); aggregate 2618 passed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
`ParticleEngine::generate_frames` has always been real, seeded, and
deterministic, but the CLI dropped `type: "particle"` on the floor as a
"runtime construct" — so authoring an ambient effect meant hand-tracking
motes across frames by eye (the embers in the hearthfire rework were
placed by hand for want of this).
`pxl render <file> --frames N` now simulates the emitter and writes a
frame-set: a numbered PNG per frame (`{name}_NN.png`, the validator's
frame-set convention) or, with `--gif`, one animated GIF. `--canvas WxH`
sizes the output (default 32x32), `--origin X,Y` places the emitter
(default bottom-center), `--fps` sets the GIF rate, and `--sprite` picks
one system when a file declares several. The emitted sprite is resolved
through the normal registry path, so it can use `extends`, transforms, or
any palette.
Because the engine is seeded, re-baking reproduces byte-identical frames
— safe to commit and diff. A missing/unknown particle sprite, a
zero-frame request, or a particle-less file all error clearly instead of
producing nothing.
Docs: new format/particle.md documents the (previously undocumented)
particle format and the baking flags; linked in SUMMARY. Tests: a CLI
integration suite covering PNG frames, GIF, determinism, and the
no-particle error.
Suite: aggregate 2622 passed / 1 pre-existing unrelated fail
(install::tests::test_install_skips_cached).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Many animation cycles — a walk bob, a shiver, a float — are only a small
offset of a base frame, yet authoring one meant re-deriving every member
region's coordinates by hand, which is both verbose and the source of
frame-tear bugs (shift fifteen regions, miss one, the sprite rips).
Two composable pieces:
- A region-level `translate: [dx, dy]` modifier shifts a region's
rasterized pixels, applied last (after every shape op, modifier, and
`symmetric`). Because it lives in the rasterizer, `render`, `mask`, and
the validator all honor it for free.
- A sprite-level `translate: { by: [dx, dy], regions: [..] }` shifts a
named group (or all regions, if `regions` is omitted) in one line. It
expands into per-region `translate` offsets at resolution, composes
additively with any a region already carries, and runs after `extends`
inheritance — so a passing frame is `extends` the contact frame plus one
`translate` line, no coordinate math.
Resolution applies the group expansion in SpriteRegistry; the single-sprite
render path routes `translate` sprites through resolution, and the `mask`
inspector and validator apply it too, so every surface agrees.
Docs: group-translation section in format/sprite.md. Tests: rasterizer
pixel-shift, group expansion (named subset and all-regions), and
mask-resolution.
Suite: aggregate 2626 passed / 1 pre-existing unrelated fail
(install::tests::test_install_skips_cached).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
`auto-outline` validated but drew nothing — the canonical silent no-op that motivated the empty-region error. Outlining is the most common pixel-art operation, so the standing workaround was the "silhouette trick": author the shape twice, once as a dark silhouette and once inset by a pixel as the fill, keeping both copies in sync by hand. Now it works: the source region is dilated outward (new `shapes::dilate`, 8-connected so convex corners fill) and the source subtracted, leaving a band `thickness` pixels wide around it (default 1). Like `auto-shadow`, it references another region, so it is deferred to the second rasterization pass everywhere regions are rasterized — renderer, `mask`, and validator. A missing source resolves to zero pixels and warns, exactly as a typo'd reference should. The validator's empty-region check no longer special-cases auto-outline as "does not render"; that suggestion now points at the real cause (a missing/empty source). Docs: regions.md auto-outline section rewritten with the z-order note. Tests: dilate doctest, rasterizer outline + missing-source, and a validator test that auto-outline no longer trips the empty-region warning (the old empty-region test now uses an empty point list as its zero-pixel case). Suite: aggregate 2630 passed / 1 pre-existing unrelated fail (install::tests::test_install_skips_cached). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
`pxl show` errored on the structured-regions format with "grid format is deprecated" — dead for the only format the project uses, while its own docs advertised an in-terminal preview. The onion-skin animation path already rendered sprites fine; only the standard single-sprite path was stubbed out. It now resolves the target through the sprite registry (so `source`, `extends`, and `translate` compose exactly as in `pxl render`) and prints an ANSI half-block true-color preview — the visual complement to `pxl mask`'s text token grid. An empty/region-less sprite reports a clear error instead of color codes. Docs: show.md gains a `mask` cross-reference; its pre-existing (previously aspirational) "basic preview" examples are now accurate. Tests: a CLI suite covering a structured sprite and an extends+translate frame. Suite: aggregate 2632 passed / 1 pre-existing unrelated fail (install::tests::test_install_skips_cached). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…nused warning) A palette token used only by an `extends` frame that omits its palette was recorded under the empty palette name, so the inherited named palette saw it as never-used and `validate --strict` failed with a spurious "tokens never used" warning — exactly the case a frame-set hits when the shared palette carries a color only a later frame paints with. The validator now remembers each sprite's (own named palette, extends target) and defers the token usage of palette-omitting extends frames, crediting it at end-of-file to the first palette up the extends chain (cycle-guarded). Regression test: a token painted only by an extending frame no longer trips the unused-token warning. Found while re-verifying the A3 features against target/release/pxl for the pixel-art skill refresh. Suite: aggregate 2633 passed / 1 pre-existing unrelated fail (install::tests::test_install_skips_cached). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
scbrown
force-pushed
the
integrate/dperegolise
branch
from
September 26, 2026 04:47
9390318 to
209611e
Compare
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PXBCFZMofE9EGKzo8J2YKJ
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Rebases the 9 commits from @dperegolise's fork (dperegolise/pixelsrc main, 2026-06-10) onto main, authorship preserved. They were never opened as a PR upstream.
extends: inherit and patch regions per keypxl render --frames: bake particle systems to framestranslatepxl show: ANSI color preview of structured spritesReviewed the diff: no Cargo/build/workflow changes. The only process spawns are the new CLI tests running
pxl. CI is the build and test gate. Rebasing after #10 lands so clippy is green.Thanks @dperegolise!
🤖 Generated with Claude Code
https://claude.ai/code/session_01PXBCFZMofE9EGKzo8J2YKJ