- fonts: IBM Plex Sans (one variable file at 400, 500 and 600) and IBM Plex Mono for the interface when built with craft-fonts (its Latin catalog, opted into from build.rs); the ui-medium and ui-semibold families always exist and fall back to the regular font; CJK fallbacks cover them too and align to the interface font's baseline - type scale 11 / 12.5 / 14 (headings semibold), values in the monospace face; spacing on a 2-4-6-8-12 scale (rows 4 apart, button padding 8x3, window margins 12); windows share the card radius - panel tabs, section captions and window titles in the medium weight, as written instead of small capitals - layer rows: the opacity field fits its slot (compact padding, small print) - Settings: every section an aligned two-column form, like Export and New project - tests/ui_snapshots.rs: renders the whole window offscreen (egui_kittest + wgpu) to PNGs for checking layout without a visible screen; AGENTS.md says how - ATTRIBUTION, NOTICE and packaging list the Plex faces and ship their licences
10 KiB
AGENTS.md: guide for AI agents and contributors
TextureCraft is an open-source, native 3D texture painter: a clean-room implementation of the
workflow known from Adobe Substance 3D Painter, written in Rust only (no JavaScript or TypeScript). No Tauri, Electron or webview
shells: the desktop app is native egui/eframe on wgpu, and the web build is the same Rust compiled
to WebAssembly (trunk + wasm-bindgen). Never add Tauri (or any webview/JS UI framework) as a
dependency, build step or packaging target. The product name is always written TextureCraft
({Function}Craft in PascalCase, like its siblings PhotoCraft, VectorCraft, FilmCraft, LightCraft,
PdfCraft, EffectCraft, DesignCraft) in user-facing text: UI, window titles, About, installers,
release names, docs prose. Machine names stay lowercase: crates (texturecraft-*), binaries, file
names, ids (ai.storyteller.texturecraft), the native project extension .tcraft.
Standards and learnings shared across the crafting apps live in ../craftrules (checked out next
to this repo, or storytold/craftrules). Read its AGENTS.md at the start of a session, follow its
standards, and contribute reusable learnings back there. Shared code goes only in craft-libs
(standards/shared-libraries.md); otherwise copy, don't depend.
The goal is 1:1 parity with Substance 3D Painter's workflow (same panels, menus, shortcuts, layer
stack semantics and export conventions) with better performance, every feature drivable by agents,
and builds for Windows, macOS, Linux, FreeBSD and the web. Read this file first, then docs/.
1. Orientation (5 minutes)
| Read | Why |
|---|---|
docs/architecture.md |
Crate map, layers, the CPU texel engine + GPU viewport split, data flow |
docs/control-protocol.md |
The JSON control channel and MCP: how agents drive the running app |
ROADMAP.md |
Honest parity estimate, milestones, current focus |
docs/parity.md |
Generated list of Painter menu items: live or missing |
docs/releasing.md |
Versioning (cargo xtask version), the release branch, packaging |
ATTRIBUTION.md |
Every bundled asset with author, source, license |
crates/<name>/README.md (where present) |
Public API of that crate |
2. Workspace map
crates/
core texel geom L0 document model + blend math; sparse COW tiles, padding, noise; meshes, BVH, UV G-buffer, camera
compose bake L1 CPU layer-stack compositor (the oracle) + display conversion; mesh map bakers
paint formats L2 brush engine (strokes, stamps, visibility, commit); mesh import, texture export, .tcraft
engine render L3 Session + command registry (every action is a command); wgpu viewport renderer
automation L4 MCP server + headless JSON-lines RPC + bridge to the running app
ui-egui L5 egui shell (thin: every action goes through engine commands)
apps/
texturecraft desktop app (eframe/wgpu), `--control <port>` loopback control server
texturecraft-cli headless CLI (info, run, commands, bake, export, render, mcp, serve)
texturecraft-web the same app in the browser (trunk + wasm-bindgen)
xtask/ cargo xtask layers | wasm | ci | version | ico | icns | parity
Layering is enforced by cargo xtask layers: a crate may depend only on lower layers. Nothing
below ui-egui may use egui, eframe, winit or rfd; only render, ui-egui and the apps may use
wgpu. A new crate must be registered in xtask/src/layers.rs.
The UI crate is swappable. texturecraft-ui-egui holds no document logic: it calls engine
commands (texturecraft_engine::execute) and the session's stroke API. Another front end could
replace it without touching the engine.
3. Golden rules
Assets, icons and images (absolutely essential)
- Never use any icon, image, logo, texture, material, brush, environment, font or other asset
from Adobe, Avid, Image-Line or Autodesk products, and never copy files from any installed
commercial application (proprietary formats such as
.sbsar,.spexp,.sppror.spsm, HDR environments, UI icons). Not as reference files in the repo either. - Every image or icon must be open source, public domain, Creative Commons (CC0/CC-BY), or original (made by a contributor who has the right to license it). Prefer generating content in code (TextureCraft's environments, materials, alphas, meshes and thumbnails are all procedural).
- Every bundled asset gets a row in
ATTRIBUTION.md(path, title, author, source, license) in the same change that adds it, plus its license file next to it when the license requires one. Assets compiled in through dependencies (icon fonts, default fonts) get a row too. No row, no merge. - Demo images and README screenshots show TextureCraft rendering original or public-domain content only. Never personal photos.
- The ArtCraft logos in
docs/brand/are not open source (docs/brand/LICENSE-brand.txt). - Fonts live in storytold/craft-fonts; never commit font files here.
Clean room
TextureCraft is an independent, clean-room implementation of a professional texture-painting
workflow. Work from public documentation, general domain knowledge, public algorithms
(GGX/Smith/Schlick, split-sum IBL, MikkTSpace, jump-flood/BFS dilation, Möller–Trumbore, SAH BVH,
spherical harmonics, reoriented normal mapping, W3C blend modes) and public engine conventions
(glTF, Unreal, Unity packing). Never copy proprietary code, shaders, presets or file formats;
Painter file compatibility (.spp, .spexp, .sbsar) is a non-goal. Always describe TextureCraft
as a clean-room implementation, in code, docs and commits.
Never crash (outranks feature work)
People trust TextureCraft with hours of painting. A malformed mesh or project, a bad command or MCP
param, an odd keystroke or a full disk must produce an error the user or agent can act on, never a
panic. The shared standard is ../craftrules/standards/never-crash.md.
- Non-test code never panics: no
unwrap(),expect(),panic!,unreachable!,todo!orunimplemented!. Every production crate root carries#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)]. - No
unsafe(unsafe_code = "forbid"for the workspace). - Input-derived numbers are hostile:
get()instead of[i], checked/saturating arithmetic, caps on allocations (mesh triangles, image sizes, zip entries), NaN guards. - Every crash fix comes with a regression test. The engine's
panic_hunttest runs every command with hostile params; keep it green when adding commands.
Architecture rules
- Everything is a command. New user-visible behaviour = a command in the engine
(
crates/engine/src/*_cmds.rs, registered incommands.rs) with id, label, menu path, shortcut, params doc,enabledandrun, plus tests. The menus, shortcuts, CLI, control channel and MCP all dispatch commands by id. Menu items come from the catalog incrates/ui-egui/src/menus.rs; an item goes live when its id is implemented. - Programmatic calls never open dialogs. A command run through the control channel or MCP runs
with defaults; only
ui.menu.invokebehaves like a menu click. - Inspect shows the agent's work.
project.inspect/session.inspectmust reveal every layer, mask, effect, fill source and painted tile count an agent changed. - CPU oracle, GPU display. Layers live as sparse copy-on-write 64×64 tiles; compositing, painting, baking and export run on the CPU per dirty tile (rayon) and are fully testable; dirty tiles are uploaded to wgpu textures for the viewport. Any GPU acceleration must match the CPU compositor in parity tests.
- Undo is snapshots.
Session::editclones the document (copy-on-write tiles make this cheap) and restores it on error, so every command is atomic. - Never break wasm. L0–L5 must
cargo check --target wasm32-unknown-unknown(cargo xtask wasm). File-system code iscfg(not(target_arch = "wasm32"))with byte-based alternatives. - Performance is a feature. Never scan a full texture set per frame; work on dirty tiles. Benchmark heavy paths (bake, composite, export) in release and record numbers in the dev log.
- Verify UI changes visually. Launch with
--control <port>and callui.screenshot(whole window) orviewport.screenshot; look at the PNG. Without a visible screen (a locked machine, CI), render the whole window offscreen:TC_UI_SNAPSHOTS=<dir> cargo test -p texturecraft-ui-egui --release --test ui_snapshots -- --ignored(addCRAFT_FONTS_DIR=<craft-fonts checkout>to see the release fonts).
4. Picking work
ROADMAP.md→ Current focus and the parity table.docs/parity.md→ missing menu items, grouped by menu.- Run the app, take screenshots of TextureCraft and fix the biggest visible gaps in the workflow.
log/devlog.md(local) → the "Still open" bullets of recent entries.
5. Before you finish a task
cargo fmt --all
cargo test -p <crates you touched> --release
cargo clippy --workspace --all-targets -- -D warnings
cargo xtask layers
cargo xtask wasm # if you touched L0–L5
cargo test -p texturecraft-engine --test session panic_hunt # if you added/changed commands
Then append a terse entry to log/devlog.md (what landed, numbers, what's still open). Commit
after every arc of work that builds and passes.
6. Parallel agents
- Use your own target dir (
CARGO_TARGET_DIR=target/agent-<name>) and edit only the files you own. Shared files (engine/src/commands.rs,engine/src/lib.rs,ui-egui/src/menus.rs,ui-egui/src/lib.rs, the rootCargo.toml) get small, surgical edits; re-read before editing. - Put new commands in a new module with its own
specs()registered by one line. - Write manifests atomically; one broken
Cargo.tomlbreaks every agent's build. - Delete your
target/agent-*dir when done.
7. Where things are tracked
ROADMAP.md: milestones, parity estimate and remaining effort.docs/parity.md: generated Painter menu coverage.docs/releasing.mdand../craftrules/release/playbook.md: release builds and signing.plan/(local, gitignored): execution plan and notes.log/(local, gitignored): the dev log.