# AbstractAvatar

> Shared procedural avatars for native, terminal and browser applications.

## Document index

- [README.md](README.md): Overview and first render
- [docs/getting-started.md](docs/getting-started.md): Installation and native/browser use
- [docs/architecture.md](docs/architecture.md): Shared generation and platform ownership
- [docs/api.md](docs/api.md): Rust, Python, C and WebAssembly interfaces
- [docs/frame-contract.md](docs/frame-contract.md): Bounded descriptors, RGBA pixels and lifecycle
- [docs/rendering.md](docs/rendering.md): Compositing, antialiasing and host palette integration
- [docs/catalog.md](docs/catalog.md): Style identifiers, activity and provenance
- [docs/faq.md](docs/faq.md): Format and integration questions
- [docs/troubleshooting.md](docs/troubleshooting.md): Setup, input and resource errors
- [CONTRIBUTING.md](CONTRIBUTING.md): Development and compatibility
- [SECURITY.md](SECURITY.md): Vulnerability reporting and trust boundaries
- [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md): Contributor expectations
- [LICENSE](LICENSE): MIT license
- [CHANGELOG.md](CHANGELOG.md): User-visible changes
- [ACKNOWLEDGEMENTS.md](ACKNOWLEDGEMENTS.md): Credits and design lineage

---

## README.md

# AbstractAvatar

[![crates.io](https://img.shields.io/crates/v/abstractavatar.svg)](https://crates.io/crates/abstractavatar)
[![PyPI](https://img.shields.io/pypi/v/abstractavatar.svg)](https://pypi.org/project/abstractavatar/)
[![docs.rs](https://img.shields.io/docsrs/abstractavatar)](https://docs.rs/abstractavatar)
[![CI](https://github.com/lpalbou/AbstractAvatar/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/lpalbou/AbstractAvatar/actions/workflows/ci.yml)

AbstractAvatar generates procedural avatars from explicit activity state, time, audio level and
gaze. One Rust implementation supplies terminal, browser and native applications with a common
catalog and rendering behavior.

Use it for an assistant portrait, an activity indicator or a procedural character that responds
to your application's state. The host supplies state and presentation; AbstractAvatar generates
the picture locally.

![Procedural avatar catalog](docs/media/catalog.png)

**What's new in 0.1.1:** gaze moves the eyes only. Each catalog design declares a `gaze`
component (`"eyes"` or `"none"`); a gaze input shifts only the eyes of `eyes` designs, and every
other design draws the same frame with or without it. See the [changelog](CHANGELOG.md#011---2026-11-03).

## Start with Rust

Add the crate from crates.io (Rust 1.87+):

```sh
cargo add abstractavatar
```

```rust
use abstractavatar::{AvatarPose, AvatarStyle, Frame, Gaze, Palette};

let frame = Frame::new(AvatarStyle::Halo, AvatarPose::Speaking, 0.6, 1.2, Gaze::default());
let palette: Palette = [[0xffc060ff; 256], [0xff7070ff; 256], [0xd8e0ffff; 256]];
let rgba = frame.rgba(0.2, -0.1, 2.0 / 128.0, &palette);
```

Enable the `ffi` feature for the C ABI and WebAssembly exports, and `json` for the versioned
parameter descriptor.

## Start with Python

Install from PyPI with Python 3.10+:

```sh
python -m pip install abstractavatar
python -m abstractavatar catalog
python -m abstractavatar render --style halo --state speaking --level 0.6 --size 128 --output avatar.png
```

Wheels for Linux x86_64 and aarch64 (manylinux), macOS arm64 and x86_64, and Windows x86_64 include the compiled native library. On other platforms pip builds the
library from the source distribution, which needs Rust 1.87+. To build from a checkout, run
`python -m pip install .` in the cloned repository. See [getting started](docs/getting-started.md) for library use and platform builds.

## Integrate a player

- **Rust:** create a `Frame` and sample composited RGBA through your theme's material ramps.
- **Browser:** run the bundled WebAssembly generator and present RGBA pixels on Canvas.
- **Python/native:** request RGBA pixels through the binding or the C interface and present them
  with your UI toolkit.

Store catalog style identifiers, rather than numeric indices. Supply time explicitly, stop
animation while hidden, and use a static frame when reduced motion is enabled. Platform adapters
do not need their own geometry implementations.

The catalog includes shaded characters, particle orbs, forms, portraits, machines, nature scenes
and a woven orb. [Catalog guidance](docs/catalog.md) explains source variants and stable keys.

## Documentation

[Documentation index](docs/README.md) · [Architecture](docs/architecture.md) ·
[API](docs/api.md) · [Frame contract](docs/frame-contract.md) · [FAQ](docs/faq.md) ·
[Troubleshooting](docs/troubleshooting.md) · [Contributing](CONTRIBUTING.md) ·
[Security](SECURITY.md) · [Changelog](CHANGELOG.md)

## For AI agents

Use the [AbstractAvatar skill](skills/abstractavatar/SKILL.md) for the rendering workflow,
descriptor constraints and platform integration rules. The [LLM index](llms.txt) maps the public
documentation.

---

## docs/getting-started.md

# Getting started

AbstractAvatar generates images locally. Rust 1.87+ builds the native library; Python 3.10+ runs
the Python API and preview command.

## Install the Python package

```sh
python -m pip install abstractavatar
python -m abstractavatar catalog
python -m abstractavatar render --style halo --state speaking --level 0.6 --size 128 --output avatar.png
```

Wheels for Linux x86_64 and aarch64 (manylinux), macOS arm64 and x86_64, and Windows x86_64 include the compiled native library. Elsewhere pip builds it from the
source distribution with Rust 1.87+. From a cloned repository, `python -m pip install .` builds
and installs your checkout.

Choose a style key from the catalog output. PNG previews use the same procedural generator as
live integrations; they capture one explicit time and state.

## Present native frames

```python
from abstractavatar import Renderer

with Renderer(width=128, height=128) as renderer:
    pixels = renderer.render(style="halo", state="speaking", level=0.6, time=1.2)
    assert len(pixels) == 128 * 128 * 4
```

`pixels` contains tightly packed RGBA bytes. Your UI toolkit owns the image object and draw loop.
See [the frame contract](frame-contract.md) for dimensions, ownership and reduced motion.

## Use Rust

Add the crate from crates.io:

```sh
cargo add abstractavatar
cargo add abstractavatar --features ffi,json   # C ABI/WebAssembly exports and the descriptor
```

```rust
use abstractavatar::{AvatarPose, AvatarStyle, Frame, Gaze, Palette};

let frame = Frame::new(AvatarStyle::Halo, AvatarPose::Speaking, 0.6, 1.2, Gaze::default());
// Replace these flat ramps with your theme's 256 shades per material.
let palette: Palette = [[0xffc060ff; 256], [0xff7070ff; 256], [0xd8e0ffff; 256]];
let rgba = frame.rgba(0.2, -0.1, 2.0 / 128.0, &palette);
```

RGBA samples retain overlapping material colors. Composite the returned foreground over your
background. Build one frame per state/time combination and sample it across the image, rather
than reconstructing geometry for every pixel. See [rendering](rendering.md) for pixel footprints.

## Build for browsers

```sh
rustup target add wasm32-unknown-unknown
cargo build --release --features ffi --target wasm32-unknown-unknown
```

The WebAssembly artifact uses the same generator. A browser adapter supplies palette values and
copies RGBA pixels into Canvas. WebAssembly execution requires no geometry interpreter written
in JavaScript. See [API](api.md), [architecture](architecture.md) and
[troubleshooting](troubleshooting.md).

---

## docs/architecture.md

# Architecture

AbstractAvatar owns the catalog and deterministic procedural generation. Hosts own activity
signals, preferences, palette, scheduling and presentation.

```mermaid
flowchart LR
    I[Host input: style key, state, time, audio level/bands, gaze] --> G[abstractavatar crate: catalog, scenes, rasterizer]
    P[Host palette: Warm/Eye/Dim ramps] --> G
    G -->|Frame::rgba| RS[Rust presenters, e.g. terminal frame player]
    G -->|ffi feature| A[C ABI avatar.h: bounded handles, RGBA buffers]
    A --> PY[Python ctypes binding and PNG preview command]
    A --> N[Native image presenter]
    A -->|wasm32-unknown-unknown| W[WebAssembly module]
    W --> C[Browser Canvas presenter]
    H[Host clock, visibility and reduced-motion preference] -.-> RS
    H -.-> N
    H -.-> C
```

The [descriptor](frame-contract.md) selects built-in generation; it does not introduce a new
programming language. Native and WebAssembly builds run the same algorithms. Platform-specific
presenters translate frames into terminal cells, a canvas image or a native image object.

Shared rasterization composes shaded surfaces and material light before returning RGBA. Built-in
scene primitives and spatial indexes are bounded; presenters do not recreate the geometry or its
color blending. Rust presenters can use `Frame::rgba` with the same palette ramps as the ABI.
See [rendering](rendering.md).

Core generation has no network, microphone, model, timer, global mouse listener or UI-framework
dependency. Gaze and audio levels are inputs supplied by the host. Gaze moves only the eyes of
designs whose catalog `gaze` component is `eyes`, by a bounded offset; it never moves, tilts or
offsets a body, sphere or scene, and the host owns its smoothing. Keeping those responsibilities
explicit lets multiple applications present the same design without sharing application state.

Rendered frames use standard RGBA channel ordering. PNG is used for portable still-image
previews. WebAssembly is the browser execution format; it is not an avatar scene-file format.
The supported formats and limits are specified in [the frame contract](frame-contract.md).

Applications persist stable style keys and resolve numeric ABI codes from the runtime catalog.
See [catalog](catalog.md) and [ADR 0001](adr/0001-portable-procedural-generation.md).

---

## docs/api.md

# API

## Python

`Renderer(width=128, height=128, library_path=None)` owns one bounded native rendering handle.
Use it as a context manager or call `close()` when its host is disposed.

`render(style="halo", state="ready", level=0.0, time=0.0, gaze=(0, 0), transparent=True)`
returns owned RGBA bytes. Gaze shifts only the eyes of designs whose catalog `gaze` is `"eyes"`,
in the ready state; other designs ignore it (see [Gaze](frame-contract.md#gaze)). Store keys from `catalog()`, and use the named activity states listed
in [the catalog guide](catalog.md). An explicit `library_path` selects a trusted native build;
normal usage loads the library bundled in the Python package. Optional `bands` supplies eight
normalized audio-band levels. `set_palette(warm=..., eye=..., dim=..., ground=...)` takes three
RGB color anchors and an RGBA background; the host retains theme ownership.

`render_descriptor()` consumes the bounded parameter object described in
[the frame contract](frame-contract.md). It does not load files, URLs or executable content
specified by that object.

## Rust

`AvatarStyle` names the built-in designs; `AvatarStyle::ALL` enumerates them. `AvatarPose` names
activity. `Frame::new(style, pose, level, time, gaze)` prepares geometry for repeated sampling.
`AvatarStyle::gaze()` returns the component a gaze moves, `"eyes"` or `"none"`; `GAZE_REACH`
is the largest eye shift, `[0.05, 0.025]` of the avatar radius.
`Frame::rgba(x, y, pixel_size, &palette)` returns a composited, straight-alpha RGBA8 foreground.
`Palette` is `[[u32; 256]; 3]`: Warm, Eye and Dim shade ramps packed as `0xRRGGBBAA`.
Prepare these ramps once per theme. `pixel_size` is the full normalized pixel width, usually
`2.0 / min(width, height)`, bounded to 0..0.125; zero requests an analytic point sample.
Non-finite inputs, out-of-range pixel sizes and coordinates outside -1.5..1.5 return transparent
black. Coordinates are centered, with y downward and one unit equal to the avatar radius.

`Frame::sample(x, y)` remains available as a single material/light/coverage approximation.
Use `rgba` to retain overlapping material colors, translucent ribbons and opaque shaded surfaces.
The native, Python and WebAssembly renderers use this same compositing path. See
[rendering](rendering.md) for antialiasing, palette behavior and scheduling boundaries.

The `ffi` feature supplies the native C/WebAssembly adapter. Geometry remains usable without a
UI toolkit. The optional descriptor feature parses the versioned parameter object.

## Native and WebAssembly

[`avatar.h`](../avatar.h) is the C ABI reference. The `avatar_*` exports enumerate metadata,
create and release handles, set palettes and gaze, render a frame and expose its pixel buffer.
Catalog field 5 is the design's gaze component (`eyes` or `none`).

`avatar_render_copy` renders and copies into caller-owned memory under the native registry lock;
use it for native integrations. The borrowed-pointer functions (`avatar_render` with `avatar_pixels`) require caller synchronization.
Native callers must follow [the frame contract](frame-contract.md). Browser adapters copy pixels from WebAssembly memory
before reusing or destroying the handle. Do not persist numeric catalog codes or native handles.

The UI toolkit and application remain outside the ABI. A host supplies a frame cadence and
activity state; the generator returns a picture.

---

## docs/frame-contract.md

# Frame contract

## Parameter descriptor

A versioned JSON object describes a frame request:

```json
{
  "schema_version": 1,
  "style": "halo",
  "state": "speaking",
  "time": 1.2,
  "level": 0.6,
  "gaze": [0.0, 0.0]
}
```

A machine-readable [JSON Schema](../schema/descriptor.schema.json) defines the fields. Optional
`bands` contains exactly eight normalized audio-band levels; omitting it uses the scalar level.
The descriptor names trusted built-in generation. It contains no code, shader source, file path,
URL or library name. Unsupported versions, styles, states and fields are explicit errors.
Dimensions and palette are supplied by the renderer/host, outside the descriptor.

Time is elapsed seconds in 0..86400. Audio level is normalized from 0 to 1. Gaze uses centered coordinates
from -1 to 1 (see [Gaze](#gaze)). Numeric values must be finite; a JSON descriptor is limited to 4096 UTF-8 bytes. Runtime adapters
should use a local elapsed clock, not an absolute epoch timestamp.

## Gaze

Gaze moves eyes only. Each catalog design declares a `gaze` component:

| `gaze` | Designs | Effect of a gaze input |
|---|---|---|
| `eyes` | Designs that draw distinct eyes: pupils, irises or eye lamps (for example `hover`, `rover`, `eye`, `watch-hover`, `watch-rover`, `watch-face`) | The eyes shift toward the gaze, at most `GAZE_REACH` = 0.05 of the avatar radius horizontally and 0.025 vertically. The body, head, shell and every other part stay still. |
| `none` | Orbs, forms, cosmic scenes, nature scenes, silhouettes, the woven orb and machines without looking eyes | Ignored. The frame is identical with or without gaze. |

Gaze applies in the `ready` state; every other state ignores it so that activity motion stays
readable. The generator is stateless and frames are a pure function of their inputs, so temporal
smoothing belongs to the host: ease the gaze value toward the pointer (for example exponential
smoothing over about 150 ms), send gaze only for designs whose `gaze` is `eyes`, and return it to
`[0, 0]` after inactivity. Read the component from the installed catalog rather than hardcoding a
list.

## Raster layout

- RGBA8: four bytes per pixel in red, green, blue, alpha order.
- Rows are tightly packed with stride `width * 4`, top to bottom.
- A frame contains exactly `width * height * 4` bytes.
- Each dimension is between 1 and 512 inclusive; at most 16 native handles can be live.
- Transparent output carries alpha; the host chooses its background and palette.

Output uses straight alpha: RGB channels are not premultiplied by the returned alpha. Internally,
light and surfaces compose in premultiplied form, preserving contributions from all three host
material ramps. Pixels with zero alpha are transparent black. Native frame rendering derives
the antialiasing footprint from the dimensions, capped at 0.125 normalized units. See
[rendering](rendering.md) for the Rust sampling equivalent.

RGBA is a pixel layout, not a new media container. [PNG](https://www.w3.org/TR/png/) provides the
portable file representation for previews. The tiny descriptor is an AbstractAvatar contract;
it is not claimed to be a general procedural-media standard.

## Lifetime and scheduling

A renderer owns its handle and fixed frame buffer. Copy pixels into an owned image before
reusing or destroying that buffer. Native callers synchronize access across render, copy,
palette updates and destruction; the Python wrapper handles that boundary.

One visible view should reuse one renderer. Do not allocate a renderer for every animation
frame. Close on disposal, stop clocks while hidden, and present a static frame for reduced
motion. Expire pointer-derived gaze after inactivity rather than installing global listeners.

The host chooses the frame rate. The pose API provides bounded cadence suggestions: quiet
states need fewer frames than speaking or working states. A frame request does no background
work after it returns.

Descriptor version 1 selects one activity state for an independent frame. It does not contain
transition weights, velocity, personality, emotional interpretation or audio history. Switching
states can change the shape immediately; a future transition contract requires explicit timing,
continuity and reduced-motion semantics. Hosts should keep elapsed time monotonic across state
changes, and report observed activity and measured voice inputs rather than inventing behavior.

## Browser execution

[WebAssembly](https://webassembly.github.io/spec/core/intro/introduction.html) executes the shared
generator in the browser. Load a trusted bundled artifact, verify its ABI/catalog/memory
requirements, and give it no ambient host imports. Descriptors must never choose arbitrary
modules. The browser presenter only schedules and presents frames.

---

## docs/rendering.md

# Rendering and theme integration

AbstractAvatar generates foreground RGBA frames from trusted built-in designs. The same Rust
compositing path serves Python, native and WebAssembly hosts. Hosts provide their theme, observed
activity, time, voice inputs and optional gaze; the generator owns geometry and rasterization.
See [the API](api.md) for entry points and [the frame contract](frame-contract.md) for ownership.

## Choose a sampling interface

Use `Frame::rgba(x, y, pixel_size, &palette)` for full rendering. It preserves overlapping colored
light, soft glow, translucent ribbons and shaded opaque surfaces. `Frame::sample` supplies a
single-material approximation for consumers that need material identity instead of color.

Coordinates are centered, with y downward. One unit is the avatar radius; normal square frames
sample x/y in -1..1. The supported sampling extent is -1.5..1.5 on each axis. RGBA sampling returns
transparent black outside this extent or for invalid numbers.

`pixel_size` is a pixel's full width in normalized coordinates. For a square 128-pixel image use
`2.0 / 128.0`. Non-square or terminal hosts can use the larger of their normalized horizontal and
vertical sample steps. Values must be finite and in 0..0.125 inclusive; zero requests analytic
point sampling. A host with a larger footprint can cap it at 0.125, as the native renderer does.

Thin strokes and particles integrate a Gaussian footprint to reduce subpixel flicker. Filled
edges use analytic coverage. Footprints above 0.03125 subdivide at most twice, producing at most
16 samples per output pixel; ordinary 64-pixel and larger images need one spatial lookup.

## Supply material ramps

`Palette` contains three arrays of 256 packed `0xRRGGBBAA` shades, in Warm, Eye and Dim order.
Each ramp runs from shadow at index 0 to highlight at index 255. Build ramps once per theme,
outside the pixel loop. Alpha in every ramp is respected.

```rust
use abstractavatar::{AvatarPose, AvatarStyle, Frame, Gaze, Palette};

let palette: Palette = [
    std::array::from_fn(|i| u32::from_be_bytes([i as u8, 80, 40, 255])),
    std::array::from_fn(|i| u32::from_be_bytes([40, i as u8, 140, 255])),
    std::array::from_fn(|i| u32::from_be_bytes([i as u8, i as u8, i as u8, 255])),
];
let frame = Frame::new(AvatarStyle::WatchHover, AvatarPose::Ready, 0.0, 1.2, Gaze::default());
let foreground = frame.rgba(0.0, 0.0, 2.0 / 128.0, &palette);
assert_eq!(foreground[3], 255);
```

Composition uses premultiplied color internally and returns straight-alpha RGBA8. An output
alpha of zero has RGB zero. Composite the foreground over the host background once. The native
renderer can do that final background composition when `transparent` is false.

Three theme anchors define the available color vocabulary. A Watch-derived design can therefore
have the same silhouette and movement in a warm, cool or monochrome host theme. A source design's
independent rainbow palette is not a separate implicit palette in this contract.

## Reuse prepared frames

Build one `Frame` for a state/time/input combination, then sample it across the image. Geometry
and spatial indexes are prepared once; `rgba` and `sample` allocate no per-pixel objects. Apple
scene generation is limited to 4096 total strokes and surfaces, with 48×48 spatial indexes.
Native raster buffers remain limited to 512 pixels per dimension and 16 live handles.

Hosts choose resolution, frame cadence and visibility. Stop clocks when hidden and use a static
frame for reduced motion. Supersampling cannot restore details lost when an image is converted
to a small terminal cell grid; pixel-capable presenters can retain more of the generated image.

Version 1 is a stateless frame contract. Transition history, accumulated phase and expressive
behavior need a separately specified continuity contract; they are not inferred from a pose
label. See [the frame contract](frame-contract.md) and [ADR 0002](adr/0002-material-compositing.md).

---

## docs/catalog.md

# Catalog and activity

`catalog()` and the native catalog exports enumerate the supported designs. Each entry carries a
stable style key, display name, description, family, source reference and gaze component. `catalog.json` records
the design provenance.

The catalog preserves shaded terminal characters separately from particle designs whose names
overlap. For example, a shaded Halo and the Watch Halo remain separate choices. Existing
`halo`, `hover`, `vortex`, `eclipse`, `eye` and `rover` keys keep their meaning; Apple-inspired
variants use their own keys.

Families include orbs, forms, cosmic scenes, portraits, machines, nature and the iPhone weave.
Additional point-body shapes are distinct from the similarly named portrait/form scenes.
Platform sampling and palette can affect appearance; the shared generator supplies the geometry
and motion. The source Apple applications retain their own implementations until separately
integrated.

## Activity states

| State | Typical host condition |
|---|---|
| ready | Idle and available |
| thinking | Reasoning or preparing a response |
| working | Executing an operation |
| listening | Accepting audio/input |
| speaking | Presenting speech/output |
| waiting | Waiting for an answer or approval |
| error | An operation failed |

The application decides the state. AbstractAvatar does not infer it from a connection, model or
microphone. Audio level and optional gaze add visual input to the selected state.

## Gaze component

`gaze` names the one part of a design that may follow a host-supplied gaze. `eyes` designs
(`hover`, `eye`, `rover`, `watch-face`, `watch-eye`, `watch-lens`, `watch-dome`, `watch-valet`,
`watch-retro`, `watch-deco`, `watch-rover`, `watch-hover`, `watch-spark` and `watch-android`)
shift their pupils, irises or eye lamps by a small bounded offset while ready. Every other
design is `none` and ignores gaze. See [the frame contract](frame-contract.md#gaze).

Persist keys, not a display name or array index. Query the installed catalog rather than assuming
a fixed number or ordering beyond the documented compatibility prefix. See [API](api.md) and
[frame contract](frame-contract.md).

---

## docs/faq.md

# Frequently asked questions

## Does each platform need its own procedural interpreter?

No. Native hosts call the compiled library; browsers run the same generator compiled to
WebAssembly. Platform presenters display the frames and own the clock, palette and input state.
See [architecture](architecture.md).

## Is this a standard animation-file format?

The request descriptor is a small AbstractAvatar parameter contract. Frames use RGBA pixels and
PNG previews; WebAssembly supplies portable execution. It is not an arbitrary animation-file
loader. See [the frame contract](frame-contract.md).

## Does rendering need an AI model, microphone or server?

No. Your application supplies activity and optional audio levels. Generation is local and does
not capture audio or make network requests.

## Must a general-purpose TUI depend on avatars?

No. A generic frame player can present application-supplied frames independently. Applications
opt into an avatar source when they want one.

## Are all source clients automatically migrated?

No. The shared catalog and generator are reusable independently of client adoption. Watch and
iPhone implementations remain separate until explicitly integrated. See [catalog](catalog.md).

## Will every host reproduce the Apple colors and transitions exactly?

The shared designs retain their own geometry and activity cues. Each host supplies three theme
material ramps, so color follows those anchors rather than an independent hardcoded Apple
palette. Version 1 requests independent frames and does not carry the Apple engine's accumulated
transition state or audio history. Terminal cell modes also have less detail than an RGBA image.
See [rendering](rendering.md) and [the frame contract](frame-contract.md).

---

## docs/troubleshooting.md

# Troubleshooting

## The native library cannot be loaded

Install the published wheel with `python -m pip install abstractavatar`, or build from source with
Rust 1.87+ and `python -m pip install .`. A source checkout alone does not include a compiled
native library.
Use `library_path` only for a trusted explicit build. See [getting started](getting-started.md).

## A style or descriptor is rejected

Run `python -m abstractavatar catalog` and use a supported stable key. Check schema version,
state, finite numeric values and unknown fields against [the frame contract](frame-contract.md).
Do not pass file paths, URLs or shader code as a descriptor.

## Frames are empty or look stretched

Confirm width/height, tightly packed RGBA channel order and image stride. Keep the intended
aspect ratio; use a transparent image over your UI's background. Verify the palette is populated
when using the low-level C interface. See [API](api.md).

## The renderer refuses another handle

Reuse a renderer across frames and close renderers belonging to disposed views. Galleries
should create only the visible presenters, or render preview images sequentially. Do not keep
one live renderer for every off-screen catalog entry.

## Animation uses CPU while hidden

Stop the host's frame clock when the view is hidden or reduced motion applies. The library has
no background animation thread. A static error/idle picture does not need a continuous redraw.
See [lifetime and scheduling](frame-contract.md#lifetime-and-scheduling).

---

## CONTRIBUTING.md

# Contributing

Use Rust 1.87+ and Python 3.10+. Build the library before trying its native integrations:

```sh
cargo test
cargo test --all-features
cargo build --release --features ffi
python -m pip install .
```

Keep procedural geometry in the shared Rust implementation. UI-specific clocks, preferences,
colors, focus and input capture belong in the host. New styles need distinct visual behavior,
source attribution, valid catalog metadata and examples of all supported activity states.

Preserve existing style keys and the published ABI contract. Add rather than repurpose catalog
entries. Changes to ownership or frame semantics belong in [architecture decisions](docs/adr/0001-portable-procedural-generation.md).

Document public API changes in [the API reference](docs/api.md) and [changelog](CHANGELOG.md).
Keep documentation indexes current with `python scripts/docs.py`; `python scripts/docs.py --check`
checks freshness without writing files.

For raster changes, compare native and WebAssembly output with identical ramps and inputs:

```sh
rustup target add wasm32-unknown-unknown
python scripts/build_artifacts.py
python scripts/check_portability.py --native artifacts/libabstractavatar.so
```

The comparison uses Node.js and a built native library. On macOS use
`artifacts/libabstractavatar.dylib`; on Windows use `artifacts/abstractavatar.dll`.
It exercises every style/state at 64 pixels plus the focused particle, ribbon and shaded-surface
designs at 32 and 128 pixels, including partial material alpha and eight voice bands.
Visual review still matters: pixel agreement establishes portability, not design fidelity.

## Releases

A release publishes one version on two registries:

- **crates.io** (`abstractavatar` crate): published by the maintainer from a local checkout with
  `cargo publish`. The release workflow does not publish the crate.
- **PyPI** (`abstractavatar` distribution): published by
  [`.github/workflows/release.yml`](.github/workflows/release.yml), which builds an sdist and
  wheels for Linux x86_64/aarch64 (manylinux), macOS arm64/x86_64 and Windows x86_64.

The version in `Cargo.toml`, `pyproject.toml` and `python/abstractavatar/__init__.py` must be
equal and `CHANGELOG.md` must have a dated `## [X.Y.Z] - YYYY-MM-DD` section; the workflow
refuses to publish otherwise. It also refuses a version already on PyPI or not yet on crates.io.
Release in this order:

1. Bump the three version sources, fold `Unreleased` into the dated changelog section, commit and
   push to `main`, and wait for CI to pass.
2. Publish the crate: `cargo publish --locked`.
3. Push the annotated tag: `git tag -a vX.Y.Z -m "abstractavatar X.Y.Z" && git push origin vX.Y.Z`.
   The tag push builds, publishes to PyPI and creates the GitHub Release.

You can also start the PyPI lane from `main` by hand. Without `publish=true` the run only builds
and tests the distributions:

```sh
gh workflow run release.yml --ref main -f version=X.Y.Z -f publish=true \
  -f publish_confirmation=publish-abstractavatar-X.Y.Z
```

A dispatched run builds the commit of `vX.Y.Z` when that tag exists; otherwise it builds the
`main` commit and creates the tag once the build passes.

PyPI publishing uses trusted publishing, with no stored token. The PyPI project needs a trusted
publisher with project name `abstractavatar`, owner `lpalbou`, repository `AbstractAvatar`,
workflow file `release.yml` and environment `pypi`.

Report security concerns using [SECURITY.md](SECURITY.md).

---

## SECURITY.md

# Security

Report suspected vulnerabilities privately to contact@abstractframework.ai. Include the affected
version, platform, input and a minimal reproduction. Avoid posting private application data or
an uncoordinated exploit in a public issue.

AbstractAvatar renders trusted built-in procedural designs. Descriptors are bounded parameters,
not scripts or arbitrary plug-ins. Use the bundled native library or WebAssembly artifact from a
trusted build. Do not load native libraries or WebAssembly supplied by an untrusted descriptor.

Hosts remain responsible for validating incoming application messages, controlling frame cadence,
and releasing renderer resources. The native API is a trusted in-process FFI boundary; callers
must obey buffer and synchronization requirements in [the frame contract](docs/frame-contract.md).

See [troubleshooting](docs/troubleshooting.md) for input and resource-limit errors.

---

## CODE_OF_CONDUCT.md

# Code of Conduct

Treat contributors and users respectfully. Discuss ideas and evidence, welcome different levels
of experience, and give actionable feedback. Harassment, threats, discrimination and disclosure
of another person's private information are unacceptable.

Report conduct concerns privately to contact@abstractframework.ai. Maintainers may moderate or
remove participation that violates these expectations and will handle reports with discretion.

---

## LICENSE

MIT License

Copyright (c) 2026 Laurent-Philippe Albou <contact@abstractframework.ai>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

---

## CHANGELOG.md

# Changelog

## [0.1.1] - 2026-11-03

### Changed

- Gaze moves eyes only. Each catalog design declares a `gaze` component: `"eyes"` for the 14
  designs that draw distinct eyes (`hover`, `eye`, `rover`, `watch-face`, `watch-eye`,
  `watch-lens`, `watch-dome`, `watch-valet`, `watch-retro`, `watch-deco`, `watch-rover`,
  `watch-hover`, `watch-spark`, `watch-android`) and `"none"` for every other design. A gaze
  input shifts only the pupils, irises or eye lamps of `eyes` designs, by at most
  `GAZE_REACH` (0.05 of the avatar radius horizontally, 0.025 vertically), in the ready state.
  Orbs, forms and every `none` design no longer translate toward the gaze; their frames are
  identical with or without it.

### Added

- `AvatarStyle::gaze()` and `GAZE_REACH` in Rust, catalog field 5 (`gaze`) in the C/WebAssembly
  ABI, a `gaze` key in Python `catalog()` records and in `catalog.json`.

- PyPI release lane: a release workflow builds the `abstractavatar` Python sdist and wheels for
  Linux x86_64 and aarch64 (manylinux), macOS arm64 and x86_64, and Windows x86_64, and publishes
  them to PyPI through trusted publishing. Wheels bundle the native library and serve Python
  3.10+. Version 0.1.0 is published to PyPI from the `v0.1.0` release source, so the crate and
  the Python distribution carry the same version.

## [0.1.0] - 2026-11-02

First release of the `abstractavatar` crate on crates.io. AbstractAvatar is a local procedural
avatar generator: your application supplies a style key, an activity state, time, audio level and
gaze, and the crate returns composited RGBA pixels. One Rust implementation serves terminal,
native and browser hosts.

### Added

- A catalog of 44 procedural designs (shaded characters, particle orbs, forms, portraits,
  machines, nature scenes and a woven orb) with stable style keys, display names, families and
  design provenance.
- Seven explicit activity states: ready, thinking, working, listening, speaking, waiting and
  error. Audio level, eight optional audio bands and gaze add visual input to the chosen state;
  listening and speaking poses respond to measured loudness.
- Rust API: `AvatarStyle`, `AvatarPose`, `Gaze`, `Frame::new` for repeated sampling, and
  `Frame::rgba` for composited straight-alpha RGBA8 through host-owned `Palette` ramps, with
  bounded pixel footprints and antialiasing. `Frame::sample` returns a single-material sample.
- Layered material compositing: overlapping material light keeps each color contribution, and
  shaded surfaces and translucent ribbons composite in the shared rasterizer.
- `ffi` feature: a C ABI (`avatar.h`) with bounded handles (1..512 pixels, at most 16 live
  handles), palette, gaze and audio-band setters, and `avatar_render_copy` into caller-owned
  memory. The same exports build for `wasm32-unknown-unknown` for browser Canvas presenters.
- `json` feature: the versioned, inert parameter descriptor (`schema/descriptor.schema.json`).
- Python binding and `python -m abstractavatar` preview command, installed from a source
  checkout with `python -m pip install .`.

### Compatibility

- Persist style keys. Numeric catalog codes are resolved at runtime and are not preference values.
- The first six ABI catalog codes (`halo`, `hover`, `vortex`, `eclipse`, `eye`, `rover`) keep
  their ordering.
- Rust 1.87 or newer.

---

## ACKNOWLEDGEMENTS.md

# Acknowledgements

AbstractAvatar is part of AbstractFramework, created by Laurent-Philippe Albou.

The procedural catalog draws on the AbstractAssistant Apple Watch and iPhone artwork and the
AbstractTUI shaded activity avatars. Those implementations provide the design vocabulary and
provenance described in the [catalog](docs/catalog.md).

WebAssembly supplies the portable browser execution format. Python, Rust and platform UI toolkits
provide the integration surfaces. No machine-learning model is needed to render the catalog.
