M0.11: ROADMAP, CONTRIBUTING, docs/architecture and docs/testing

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Brandon Thomas 2026-10-01 23:07:08 -04:00
parent 1e8768ad18
commit c9f6f9e3be
5 changed files with 201 additions and 1 deletions

View File

@ -73,7 +73,7 @@ a command, everything is agent-drivable, and the quality gates (`cargo xtask ci`
## See also
- [CONTRIBUTING.md](CONTRIBUTING.md) and [docs/contributing.md](docs/contributing.md): setup, gates, commits, how to add things
- [CONTRIBUTING.md](CONTRIBUTING.md): setup, gates, commits, how to add things
- [docs/architecture.md](docs/architecture.md): layers, data model, commands, pipeline
- [docs/testing.md](docs/testing.md): oracle tests, criteria, benchmarks
- [docs/agents.md](docs/agents.md): driving EffectCraft over MCP / the control channel, and the agent work loop

42
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,42 @@
# Contributing to EffectCraft
Thanks for helping. Please read [AGENTS.md](AGENTS.md) first: its rules on assets and clean-room
work apply to everyone, people and AI agents alike, and they are not negotiable.
## Setup
You need [Rust](https://rustup.rs/) 1.95 or newer.
```sh
cargo run -p effectcraft # the desktop app, with the demo project
cargo test --workspace
cargo xtask ci # what every commit must pass
```
## Ground rules
- **Clean room.** Work from After Effects' public documentation and its observable behaviour only.
Never use Adobe artwork, presets or code, and never copy GPL/LGPL/AGPL code.
- **Every asset is attributed.** Add a `.attribution` sidecar and an [ATTRIBUTION.md](ATTRIBUTION.md)
row; `cargo xtask assets` checks it.
- **Layering.** Nothing below the interface layer depends on UI or OS crates; see
[docs/architecture.md](docs/architecture.md).
- **Everything is a command.** New features are engine commands with an id, label, menu path,
shortcut and parameters, so the UI, the CLI and the MCP server get them at once. Interactive
widgets register an automation id.
- **Tests with every change**, and green `cargo xtask ci` before every commit. One task per commit.
## How to add…
- **An effect:** add an `EffectSpec` in `crates/effects` (AE display name, category, parameters
with AE's names, defaults and ranges) and its render function, with a determinism test and a
behaviour test. If it reads the clock directly, add it to `TIME_DEPENDENT`.
- **A command:** register it with `cmd!` in `crates/engine/src/commands/`, place it in the menu
tree in `crates/engine/src/menus.rs`, and test it including undo.
- **A panel or dialog:** `crates/ui-egui/src/panels/`; look at the result with the headless
snapshot tool described in [docs/testing.md](docs/testing.md).
## Talk to us
Questions, ideas and bug reports are welcome on [Discord](https://discord.gg/artcraft) and in
GitHub issues.

25
ROADMAP.md Normal file
View File

@ -0,0 +1,25 @@
# Roadmap
EffectCraft aims to do what After Effects does, with the same panels, menus and behaviour, written
from scratch in Rust. This is where it stands. The milestones overlap; several are worked on at
once.
| | Milestone | State |
|---|---|---|
| M0 | Skeleton: crates, compositor, the After Effects style shell, control channel, `cargo xtask` | Done (web build pending) |
| M1 | Keyframes: temporal and spatial interpolation, Easy Ease, roving, velocity | Done |
| M2 | Compositing: 38 blend modes, track mattes, parenting, adjustment layers | Done |
| M3 | Project operations: settings dialogs, layer commands, `.ecproj`, undo, After Effects menu bar | Mostly done |
| M4 | Preview: precomps, motion blur, cached playback | Mostly done |
| M5 | Timeline depth: graph editor, keyframe clipboard and dialogs, time remapping, pick-whips, expression editor | Done |
| M6 | Shapes, masks and footage: shape operators, masks and the pen tool, video and image import | Done |
| M7 | 3D: 3D layers, cameras, lights, shadows, depth of field, 3D views and camera tools | In review |
| M8 | Expressions with the After Effects object model | Done |
| M9 | Text and effects: text animators, 241 effects | In progress (layer styles, more selectors) |
| M10 | Export: render queue, H.264, ProRes, image sequences, GIF, audio | Done |
| M11 | Animation tools: presets, Motion Sketch, Wiggler, Lottie, audio playback | Next |
| M12 | Performance: layer cache and parallel compositing (done), GPU compositing, disk cache, motion tracking | In progress |
| M13+ | Time effects, puppet, paint, roto, preferences and shortcut editor, the web app, a plugin API | Planned |
| M14 | Built for agents: MCP server, command-line tool, control channel | Done |
Come tell us what matters most to you on [Discord](https://discord.gg/artcraft).

89
docs/architecture.md Normal file
View File

@ -0,0 +1,89 @@
# Architecture
EffectCraft is a stack of small Rust crates. The engine knows nothing about the user interface;
the egui frontend, the command-line tool and the MCP server all sit on top of the same `Session`
and drive it through the same command registry.
## 1. Crates and layering
Dependencies only point downward (or along the few same-layer edges listed below). `cargo xtask
layers` enforces this, and fails the build if a crate below L5 depends on egui, eframe, winit, rfd,
cpal or muda. Everything in L0 to L4 also builds for `wasm32-unknown-unknown` (`cargo xtask wasm`).
| Layer | Crate (`effectcraft-…`) | Responsibility |
|---|---|---|
| L0 | `time` | `Tick` (254 016 000 000 per second), rational frame rates incl. NTSC, SMPTE and drop-frame timecode |
| L0 | `geom` | Vectors, matrices, quaternions, the layer transform (anchor, position, scale, orientation, rotation) |
| L0 | `color` | sRGB and linear, HSL/HSV, luminance, the 38 blend modes, label colors |
| L1 | `raster` | Premultiplied float images, sampling, affine and projective warps, blurs, compositing (parallel with rayon) |
| L1 | `keyframe` | Animated values, keyframes with temporal ease and spatial Bezier, roving, hold, velocity |
| L1 | `path` | Bezier paths, path operators (trim, offset, round corners, zig zag, twist, merge…), stroking, coverage masks |
| L2 | `project` | The document: items, compositions, layers, the property tree, render queue model, `.ecproj` serde |
| L2 | `text` | Fonts, shaping, layout, per-glyph geometry, text animators and selectors |
| L2 | `effects` | The effect registry (241 effects) and their CPU implementations |
| L3 | `render` | Evaluation and compositing: sources, masks, effects, transforms, 3D, motion blur, mattes, blending, layer cache, audio mixdown |
| L3 | `media` | Footage decoding (FilmCraft's pure-Rust codecs), image sequences, frame cache |
| L3 | `expr` | The expression engine (JavaScript via boa) with the After Effects object model |
| L3 | `export` | Render queue encoding: H.264, ProRes, PNG/JPEG/TIFF/EXR sequences, GIF, audio |
| L4 | `engine` | `Session`: project, undo history, editor state, the command registry and menus |
| L4 | `host` | A fully wired `Session` (media, expressions, exporter) for the frontends |
| L5 | `ui-egui` | The desktop interface: docking, panels, viewer, timeline, graph editor, dialogs, control channel |
| L5 | `automation` | The MCP server, headless or bridged to the running app |
| L6 | apps `effectcraft`, `effectcraft-cli` | Desktop app; command-line tool (render, exec, get/set, MCP) |
Allowed same-layer edges: `path → keyframe, raster`, `text → path`, `effects → project, text, path`,
`media / expr / export → render`, `export → media`, `host → engine`.
## 2. Time
All time is an integer `Tick`, 254 016 000 000 per second, the least common multiple of every
broadcast frame rate (×1001) and audio sample rate, so frame and sample positions are exact.
Keyframe times are in **layer time**; `layer_time = (comp_time - start_time) / stretch`, and a
Time Remap property overrides the source time. Commands snap layer times, keyframe times and
comp durations to whole frames, as After Effects does. Timecode is only a display.
## 3. Document model
```
Project { settings, items, render_queue }
Item { id, name, label, parent folder, kind: Folder | Comp | Footage | Solid }
Comp { size, pixel aspect, frame rate, duration, start timecode, background, work area,
layers (index 0 = layer #1), markers, motion blur shutter, renderer }
Layer { id, name, source (footage | comp | solid | text | shape | null | camera | light),
in/out/start, stretch, switches, blend mode, track matte, parent, props }
```
Everything animatable is a `Property` in the layer's `PropGroup` tree, addressed by a path such as
`transform/position`, `effects/#1/blurriness`, `masks/#1/feather`, or by `@uid`. Effects, masks,
text animators and shape contents are groups in the same tree.
## 4. Rendering
`Renderer::comp_frame(comp, t)` walks the layers bottom to top. For each visible layer it renders
the **source** (solid, footage frame, text, shape contents, or a nested comp), applies **masks**,
then **effects** in order, then the **transform** into comp space (with motion blur sub-samples
when enabled), the **track matte**, and finally **blends** into the accumulator. Adjustment layers
apply their effects to the accumulator. Runs of 3D layers are composited per pixel through the
active camera, with lights, shadows and depth of field.
A **layer cache** keeps each layer's finished pixels (source, masks and effects) keyed by a hash of
its evaluated inputs, excluding the transform. Static and transform-only layers render once;
editing one layer re-renders only that layer. Effects that read the clock directly are declared in
`effects::TIME_DEPENDENT`, and a test checks every registered effect against that list.
Half, Third and Quarter resolution render proportionally fewer pixels end to end.
## 5. Expressions
Expressions are JavaScript, run by boa, with After Effects' object model (`thisComp`, `thisLayer`,
`time`, `value`, `wiggle`, `loopOut`, vector maths on arrays, and so on). A syntax error keeps the
text, disables the expression and shows a warning, like After Effects.
## 6. Commands, the UI seam and automation
Every action is a command: an id, a label, a menu path, a shortcut, a parameter description,
`enabled()` and `run()`. The menu bar is a tree of command ids. The desktop UI, the command line,
the JSON control channel and the MCP server all dispatch by id, so anything a person can do from a
menu, an agent can do too. Every interactive widget registers an automation id, so agents can also
inspect and click the interface. See [agents.md](agents.md) and
[control-protocol.md](control-protocol.md).

44
docs/testing.md Normal file
View File

@ -0,0 +1,44 @@
# Testing
```sh
cargo test --workspace # everything
cargo xtask ci # fmt, clippy -D warnings, tests, layers, assets, wasm
```
## What the tests cover
- **Engine commands** (`crates/engine/src/tests*.rs`): every command family, including undo and
redo, and that every menu entry resolves to a registered command.
- **Rendering** (`crates/render`): pixel checks on small compositions, for blend modes, masks,
mattes, time remapping, 3D projection, lighting and shadows. Fast paths (the layer cache, the
affine compositor, blurs, motion blur accumulation) are checked against their reference
implementations.
- **Effects** (`crates/effects`): each effect has a determinism test and at least one behaviour
test (identity at neutral settings, known pixel results, or simulations giving the same frame
whether you seek straight to it or play up to it). A registry-wide test checks that effects
which read the clock are declared time-dependent.
- **Export** (`crates/export/tests`): every format is encoded and decoded back, checking frame
count, size and pixels. When `ffmpeg`/`ffprobe` are installed they are used as an outside
check; they are never linked or shipped.
- **Automation** (`crates/automation`, `apps/effectcraft-cli/tests`): MCP protocol round trips and
the command-line tool's JSON output.
- **Interface** (`crates/ui-egui`): headless egui_kittest tests for panels and dialogs.
## Looking at the interface without a window
```sh
cargo run -p effectcraft-ui-egui --example snapshot -- --out ui.png \
--step '{"method":"engine.execute","params":{"command":"layer.select","params":{"layers":["#2"]}}}'
```
Each `--step` is a control-channel request; `{"method":"snap","params":{"path":"x.png"}}` writes an
intermediate image.
## Benchmarks
```sh
cargo run --release -p effectcraft-cli -- bench --n 10 --play 30
```
prints per-layer and per-effect timings for one frame, and playback timings with and without the
layer cache.