UnitForge Web Pixel Art Character Generator

UnitForge is a free browser tool for making 2D pixel art characters. Assemble a character from swappable parts — body, hair, helmet, armor, weapons — preview the idle, move and attack animations, then export a single PNG or a sprite sheet for any 2D engine. The Godot 4 export writes the sprite sheet, a SpriteFrames .tres and a ready-to-run scene. No signup, and characters built from the built-in parts are free to use commercially.

📖 How to Use

Each dashed box mirrors the actual control on the Character tab. The arrow links each box to its explanation.

1. Layout

Character preview (canvas)
Parts panel

Left = preview, right = parts panel

The assembled character animates in real time on the left canvas. Everything — part selection, colors, adjustments, export — is operated from the right panel. The character faces left by default and all actions (attack, move) are left-oriented.

2. Choosing Parts

Swap parts with the dropdowns

Select Body, Hair, Helmet, Armor, Boots, Weapon and Shield individually. Choose None to unequip. In the list, marks custom parts stored in this browser.

The + button on the right uploads a custom part (see section 6).

Coloring (hair)

Categories with a color swatch can be tinted. White (#ffffff) is the original color.

3. Part Adjustment (position / tilt)

X Y R

X/Y = position (px), R = tilt (degrees)

  • X: horizontal (+ right), Y: vertical (+ down) — ±8px, 1px steps
  • R: tilt — weapons & shields only, ±90°, 15° steps
  • The weapon pivots around its grip and the shield around its center — the part stays in hand and the tilt carries into attack animations naturally

4. Visor Modes (closed helmets)

Sealed / Eyes visible / Open

  • Sealed: face fully covered (default)
  • Eyes visible: helmet shape intact, face shows only through the visor slit
  • Open: the whole face window opens — the helmet's interior is visible when worn alone

Applies only to closed-type helmets (marked "Closed"); caps and open helmets are unaffected.

5. Animation · Facing · Playback

Melee sword attack animation
Melee
Dagger chop attack animation
Chop
Two-hand spear thrust attack animation
Two-hand thrust
Bow attack animation
Bow
Handgun attack animation
Handgun
Long gun attack animation
Long gun
A pixel art knight looping through its idle, move and attack animations in UnitForge

Preview five motions

IDLE, MOVE, ATTACK, DAMAGED and DEATH play in real time. Use ↔ Flip to mirror the facing and ⏸ Pause to freeze a moment.

Equipping a bow or a firearm adds a dedicated attack (BOW_ATTACK / PISTOL_ATTACK) to the list — it disappears when you switch to another weapon.

6. Uploading Custom Parts

Drag & drop PNG here or click to select

Equip parts you drew yourself

Press the + button on any category to open the upload dialog and follow the spec shown there. Key rules:

  • Canvas is 32×32 dots — you may draw at N× scale (recommended 8× = 256×256, 1 dot = N×N block). Coordinates are always in dots
  • Weapon: grip at dot (23,23), blade pointing right (+x)
  • Hats/helmets (cap type): keep the face area empty (intruding pixels are auto-removed)
  • Closed helmets: paint the eye area pure black #000000 and the three visor modes are generated automatically (use near-black like #0A0A0A for outlines)
  • Use 📥 Download sample to get a correctly formatted example

Only PNG files are accepted and the file content is verified. You can drop multiple files at once (file name = part name). Uploaded parts appear with ★ and support adjustment, animation and export just like built-in parts.

Select a custom part in the list and a 🗑 button appears to delete it.

7. Backing Up Custom Parts

Keep your parts when the browser is cleared

Custom parts live only in this browser (★). Nothing is uploaded anywhere, and no account is needed — but clearing your browser data deletes them.

⬇ Export backup in the footer saves every custom part into a single .json file. ⬆ Import backup reads it back — on this machine or any other. Importing merges rather than overwrites, so re-importing the same file twice is safe.

8. Export

An exported UnitForge sprite sheet: five animation rows of eight 48×48 frames each

Single PNG / sprite sheets

  • Export PNG: one still IDLE pose (192×192)
  • Export Sheet: 8 frames of the animation chosen in "Sheet target" (48×48 native pixels per frame)
  • Choosing All stacks the available animations as rows in one sheet (e.g. 384×240 for 5)
  • Export for Godot: a ready-to-use sprite sheet + SpriteFrames (.tres) + optional scene (.tscn) for Godot 4 — drop them in your project and it plays
  • Part adjustments, tilt and facing are reflected in exports
  • The red dashed box on the stage shows the export frame — pixels outside it are clipped

9. Monsters tab

The Monsters tab is a ready-made bestiary — 17 creatures across many rig types (large humanoids, slimes, ghosts, bats, four-legged beasts, bugs, a mushroom, a mimic, a serpent). Pick one, choose a color (preset swatch or any color — applied to exports too), preview its 5 animations, and export a PNG or sheet just like a character.

  • Monsters are single-piece rigs — no equipment slots
  • DEATH plays once and holds the final pose

10. Character Editor tab

The Character Editor lets you draw a custom part pixel by pixel, or load an existing one and edit it. Pen / eraser / fill / eyedropper, a palette, undo·redo (Ctrl+Z / Ctrl+Y), right-click to erase.

  • Pick a category, then Load a base to edit or start from “— New —”
  • The faint body is a placement guide; red dashes = face window (auto-cleared for caps), blue cross = grip/center anchor
  • Everything faces the same left direction as the Character tab; the preview shows the equipped result live. Adjust X/Y shifts the part in both views; R (tilt) is applied to the preview and the saved part only — the drawing canvas always stays upright and axis-aligned so one pixel is always one square on the grid
  • Save as part adds it to the Character tab's list (★ this browser) — the same pipeline as upload, so ground-rules (face window, closed-helmet 3 modes) apply automatically

11. Copyright

All built-in parts are original assets. You are solely responsible for the copyright and legal standing of sprites you create or export and custom parts you upload. See 📢 Notice in the top-right for details.

Guide for AI

These days, AI agents do the building. The specification below is a complete, tested operating manual for UnitForge — every selector, part, animation and export format. Copy it and paste it to your AI (or give it the URL on the right), then ask in plain language, for example:

"Make a dark knight holding a rifle, face it right, and export the full sprite sheet."
"Make a red-haired ranger with a bow and export a PNG portrait."
"Upload my spear.png as a two-hand thrust weapon and preview SPEAR_ATTACK."
Direct URL for AI agents: unitforge.net/ai-guide.md
# UnitForge — AI Agent Guide

Spec-Version: 2026-08-22.3

UnitForge (https://unitforge.net) is a web-based pixel-art character maker. It assembles a 21-pixel chibi character from interchangeable parts (body, hair, helmet, armor, boots, weapon, shield), plays skeleton-driven animations, and exports game-ready PNG stills and sprite sheets. This document is a complete operating manual for an AI agent driving the site via browser automation, or for instructing a human step by step. Every selector and behavior below is verified against the live site.

This file is always available at: https://unitforge.net/ai-guide.md

## 1. Core concepts

- Character: 21 px tall retro pixel chibi, 3/4 view. **Faces LEFT by default**; use the Flip button for right-facing.
- Skeleton-driven: parts attach to bones (head, body, two arms, two feet). Animations are bone transform curves only — every equipped part follows automatically. No per-part animation work is ever needed.
- Rendering is nearest-neighbor (crisp pixels). The stage shows a 10× preview; exports are native resolution.
- The **red dashed box** on the stage is the export frame (48×48 native px). Pixels outside it are clipped in exports.
- The UI is bilingual (English default / Korean). Control selectors below are language-independent.

## 2. UI map — stable selectors

Each screen has its own URL, so an agent can navigate straight to it instead of
clicking through tabs. English lives at the root, Korean under `/ko/`.

| Screen | URL | Korean |
|---|---|---|
| Character | `/` | `/ko/` |
| Monsters | `/monsters` | `/ko/monsters` |
| Character Editor | `/editor` | `/ko/editor` |
| Guide | `/guide` | `/ko/guide` |
| Guide for AI (this document) | `/ai-guide` | `/ko/ai-guide` |

The raw markdown of this document is also served at `/ai-guide.md`.
Tabs are real links (`a.tab-btn[data-tab="…"]`); clicking one swaps the view via
pushState without a full page load.

All character-assembly controls live on the **Character** tab (the default tab; other tabs are Monsters, Character Editor, Guide, Guide for AI and are not needed for assembly). Dispatch a `change` event after setting a `<select>`, and an `input` event after setting a number/color input, or interact by real clicks/keystrokes.

| Control | Selector | Behavior |
|---|---|---|
| Part select (per category) | `select[data-cat="<Category>"]` | Option value = part index as string; `"-1"` = none/unequipped. Categories: `Body`, `Hair`, `Helmet`, `Armor`, `Boots`, `Weapon`, `Shield` |
| Position adjust | `input[data-cat="<Category>"][data-adj="x"]` / `[data-adj="y"]` | Number, −20…20 px, step 1. +X = toward facing direction (forward), +Y = down |
| Tilt adjust (Weapon/Shield only) | `input[data-cat="<Category>"][data-adj="r"]` | Number, −180…180°, step 15. Weapon pivots at its grip, shield at its center |
| Hair tint | `input[data-tint="Hair"]` | Color input; recolors the hair |
| Helmet mode | `select#helm-mode` | `sealed` / `eyes` / `open` — affects closed-type helmets only (see §4) |
| Animation | `select#anim-select` | Values listed in §5. Conditional attack entries appear only when a matching weapon type is equipped |
| Flip left/right | `button#btn-flip` | Toggles facing. Current direction is readable from its `data-facing` attribute: `"left"` (default) or `"right"` |
| Play/Pause | `button#btn-play` | Toggles animation playback |
| Export PNG | `button#btn-png` | Downloads `unitforge_character.png` (see §6) |
| Sheet target | `select#export-anim` | Set by option **value**: `ALL` or an animation name. (Visible labels are localized — e.g. the ALL option displays "All"/"전체" — so match by value, not text) |
| Export sheet | `button#btn-sheet` | Downloads the sprite sheet for the sheet target (see §6) |
| Export for Godot | `button#btn-godot` | Opens a dialog; exports `.png` + `.tres` (+ optional `.tscn`) for Godot 4 (see §6) |
| Preset: default knight | `button#preset-default` | Sword + shield + full knight set |
| Preset: all off | `button#preset-alloff` | Bare base body |
| Preset: random | `button#preset-random` | Random part per category |
| Upload custom part | `button[data-upload="<Category>"]` | Opens the upload modal for that category (see §7) |
| Delete custom part | `button[data-del="<Category>"]` | Visible only while a custom part is selected |
| Export backup | `button#btn-backup` | Downloads every custom part as a single `unitforge-parts.json` |
| Import backup | `button#btn-restore` | Opens a file picker; merges a backup `.json` back in (duplicates are skipped) |

Upload modal fields: `#upload-name` (text), `#upload-file` (file input, PNG only, multi-file allowed), `#upload-form` (form/type dropdown — shown for Helmet and Weapon), `#upload-sample` (downloads a correctly-formatted sample PNG), `#upload-confirm` / `#upload-cancel`.

## 3. Built-in parts catalog

<!--PARTS_START-->
| Category | Built-in parts | Controls |
|---|---|---|
| Body | Base 01, Orc, Goblin, Skeleton, Zombie | always on (not removable), tintable (skin tone via `input[data-tint="Body"]`, multiply — darker tones only), custom bases can be uploaded/drawn (6-cell) |
| Hair | Brown, Blonde, Black, Long, Ponytail, Spiky, Bob, Mohawk | removable, tintable, adjust X/Y |
| Helmet | Knight 01 (Closed), Open 01, Viking, Kettle, Crested, Leather Cap, Gold Helm (Closed), Dark Helm (Closed), Ranger Hood, Mage Hat, Paladin Wing (Closed) | removable, adjust X/Y |
| Armor | Knight 01, Leather, Gold, Dark, Ranger, Mage Robe, Crimson, Paladin, Bronze, Frost | removable, adjust X/Y |
| Boots | Knight 01, Leather, Gold, Dark, Ranger, Mage, Crimson, Paladin, Bronze, Frost | removable, adjust X/Y |
| Weapon | Sword 01, Axe, Hammer, Dagger *(chop)*, Bow *(bow)*, Dagger Red *(chop)*, Staff, Mace, Sword Silver, Sword Bronze, War Pick, Pistol *(handgun)*, Rifle *(long gun)*, Musket *(long gun)*, Revolver *(handgun)*, Spear *(two-hand thrust)*, Trident *(two-hand thrust)* | removable, adjust X/Y/R |
| Shield | Shield 01, Round Wood, Tower, Round Red, Kite Blue, Gold, Green, Crystal, Dark Gem, Kite Red | removable, adjust X/Y/R |
<!--PARTS_END-->

Weapon behavior column meanings — see §4. Custom uploads are appended to the same selects, prefixed `★`.

## 4. Weapon types and conditional animations

Each weapon has a behavior type. Equipping it adds its attack animation to `#anim-select` and `#export-anim` (and removes others'):

| Type | Attack animation | Behavior |
|---|---|---|
| melee | `ATTACK` (generic) | Overhead swing. Held behind the body, blade up |
| chop (dagger) | `CHOP_ATTACK` | Arm raises to 120°, slams down to 50°. Weapon and arm render in front during the attack |
| two-hand thrust (spear) | `SPEAR_ATTACK` | Outside combat: carried upright like a normal weapon. During attack: rotated 90° at the grip and held level like a rifle, both hands on the shaft (front hand on top), backswing then forward lunge |
| bow | `BOW_ATTACK` | Front arm extends to the bow, draw, release snap |
| handgun | `PISTOL_ATTACK` | One-hand aim; the front arm stays down and only recoils |
| long gun | `RIFLE_ATTACK` | Two-hand aim (front hand supports the stock, rendered above the gun). Outside combat the gun is hidden behind the body |

Helmet modes (`#helm-mode`) apply to helmets marked "(Closed)": `sealed` = fully closed, `eyes` = eye slit opened, `open` = full face visible with helmet interior behind the head. Other helmets ignore the mode. Equipping any helmet hides hair.

## 5. Animations

| Name | Duration (s) | Loop | Notes |
|---|---|---|---|
| IDLE | 2.0 | yes | Breathing bounce |
| MOVE | 0.6 | yes | Walk cycle, feet keep ground contact |
| ATTACK | 0.5 | yes | Generic melee swing |
| CHOP_ATTACK | 0.5 | yes | Dagger only (conditional) |
| SPEAR_ATTACK | 0.6 | yes | Spears only (conditional) |
| BOW_ATTACK | 0.9 | yes | Bow only (conditional) |
| PISTOL_ATTACK | 0.6 | yes | Handguns only (conditional) |
| RIFLE_ATTACK | 0.6 | yes | Long guns only (conditional) |
| DAMAGED | 0.4 | yes | Knockback flinch |
| DEATH | 0.8 | no | Falls backward, ends lying down |

## 6. Exports

- **PNG** (`#btn-png`): one still IDLE pose, 192×192 px (48 native × 4). Always IDLE regardless of the selected animation.
- **Sheet** (`#export-anim` + `#btn-sheet`): 8 frames per row, **48×48 native px per frame**. One animation = 384×48. `ALL` = one row per available animation, top to bottom in the `#export-anim` listed order (e.g. 6 rows = 384×288 when a conditional attack is present).
- Frame times: looping animations sample t = i·(duration/8) for i = 0…7 (no duplicated end frame); DEATH samples t = i·(duration/7) so the final lying pose is included.
- Part adjustments, tilt, facing (flip), helmet mode, and tint are all reflected in exports.
- **Godot 4** (`#btn-godot` → dialog): exports three files — the sheet `.png`, a `SpriteFrames` `.tres` (one animation per sheet row, per-animation fps = 8/duration, `loop=false` only for DEATH) and, when `#godot-autoplay` is checked, a ready-to-run `.tscn` (AnimatedSprite2D, `texture_filter = 1` nearest, autoplay on the first animation). Dialog fields: `#godot-name` (file base name), `#godot-scale` (1/2/4 → 48/96/192 px frames), `#godot-autoplay`; confirm with `#godot-go`. Put all files in the same Godot project folder.
- Engine import settings: grid 48×48, pivot at bottom-center (ground line is 6 px above the frame's bottom edge), filter **Point/Nearest**, no compression, no mipmaps. Suggested PPU: 32.

## 7. Custom part upload

Per category, click `button[data-upload="<Category>"]`. Rules (violations are rejected or auto-corrected):

- **PNG only**, validated by file signature (renaming other formats does not bypass it).
- Canvas: **32×32 px per cell**, drawn at assembly position (the sample from `#upload-sample` shows the exact expected layout — download it and draw over it). Integer upscales (64×64, 96×96 … = N× grid) are auto-detected and downscaled.
- Multi-cell categories use one horizontal strip: **Body** = 192×32 (head | torso | left arm | right arm | left foot | right foot), **Armor** = 96×32 (torso | left shoulder | right shoulder), **Boots** = 64×32 (left | right). Others are single 32×32.
- Weapon blade / boot toe must point **+x (right)** in the art. Weapon grip is at (23,23), shield center at (10,21).
- Helmet/Hair: pixels inside the face window (columns 11–21, rows 12–16) are auto-removed so the face never gets painted over. For a **closed** helmet, draw the eye slit in **pure #000000** inside the face window — the three view modes (§4) are derived from it automatically. Choose form (cap / closed) in `#upload-form`.
- Weapon: choose the behavior type (§4) in `#upload-form`: melee / chop / two-hand thrust / bow / handgun / long gun.
- Storage: parts persist in this browser only (localStorage) — there is no account and nothing is uploaded. Use `button#btn-backup` / `button#btn-restore` to move them between browsers or machines as a `.json` file. Delete the selected custom part with `button[data-del="<Category>"]`.

## 8. Verified recipes

**R1 — Dark knight with a rifle, facing right, full sheet export:**
1. Click `#preset-default`.
2. `select[data-cat="Helmet"]` → option with text `Dark Helm (Closed)`; `select[data-cat="Armor"]` → `Dark`; `select[data-cat="Boots"]` → `Dark`.
3. `select[data-cat="Weapon"]` → `Rifle` (adds RIFLE_ATTACK), `select[data-cat="Shield"]` → `-1` (None).
4. `#anim-select` → `RIFLE_ATTACK` to preview; click `#btn-flip` for right-facing.
5. `#export-anim` → `ALL`; click `#btn-sheet`. Result: 384×288 sheet (6 rows: IDLE, MOVE, ATTACK, RIFLE_ATTACK, DAMAGED, DEATH).

**R2 — Red-haired ranger with a bow, PNG portrait:**
1. `#preset-alloff`, then `select[data-cat="Hair"]` → `Brown`, `input[data-tint="Hair"]` → `#c03030`.
2. `select[data-cat="Armor"]` → `Ranger`, `Boots` → `Ranger`, `Helmet` → `-1`, `Weapon` → `Bow`.
3. Click `#btn-png`. Result: 192×192 still IDLE PNG.

**R3 — Spearman with an open-face gold helm:**
1. `#preset-default`; `select[data-cat="Weapon"]` → `Spear`; `Shield` → `Tower`.
2. `select[data-cat="Helmet"]` → `Gold Helm (Closed)`; `#helm-mode` → `eyes`.
3. `#anim-select` → `SPEAR_ATTACK` to preview the two-hand thrust; export as needed.

## 9. Errors, limits & compatibility (the failure ledger)

- **Browser support**: evergreen browsers (Chromium, Firefox, Safari — ES modules + Canvas 2D required). Developed and CI-verified on Chromium. A responsive mobile layout exists (≤820px width); the desktop layout and all selectors are unchanged by it.
- **Selector stability contract**: element `id`s and `data-*` attributes documented in §2 are the stable automation API — they are kept backward-compatible. Option **indexes are NOT stable** (parts get added); always match options by visible text (§3 catalog) or by value, never by position. The parts table in this document is regenerated on every deploy, so a re-fetch of this file is the source of truth.
- **Upload rejection receipts** (shown in the modal status line; exact English texts):
  - wrong extension → `Not a PNG file (.{ext})`
  - content is not a real PNG (magic-byte check) → `Not a PNG file (content check failed)`
  - file over 2 MB → `File too large (max 2MB)`
  - wrong canvas geometry → `Expected {w}×{h} (or an integer multiple), got {gw}×{gh}` (multiples up to 16× are accepted and downscaled)
  - helmet/hair pixels inside the face window → auto-removed with a warning, not rejected
  - browser storage full → the part is added for this session but warns that it will not survive a refresh
- **No server-side state**: there is no API, no account and no upload. Everything runs in the browser, so the only cap is the browser's own localStorage quota.
- **Known 1-px cases**: none currently — every built-in weapon × animation × frame fits the 48×48 export box (checked by an automated out-of-bounds pixel scan).

## 10. License

- Characters you assemble from **built-in parts** and export (PNG or sprite sheet) are **free to use, including in commercial projects. No attribution required.**
- Do **not** redistribute or resell the raw built-in part assets themselves (the .png part files) outside of exported characters.
- **Custom parts you upload** remain your responsibility: you must hold the rights to the images you upload, and the service disclaims liability for user-uploaded content (see the site notice).

## 11. Blind-run record

Latest verification run of this spec against the live site — one row, five receipts. Updated whenever the spec or UI changes. If the newest row is older than the Spec-Version above, treat the spec as unverified.

| Date | Spec-Version | Browser build | Recipe | Export pixel-hash (SHA-256) | First selector failure |
|---|---|---|---|---|---|
| 2026-08-22 | 2026-08-22.3 | Chrome/148.0.7778.280 | R1 (§8) | `76e03984def5fd9258475c7868f708c298b389221801a03d5cc21dfb021a31a5` | none |
| 2026-08-22 | 2026-08-22.2 | Chrome/148.0.7778.280 | R1 (§8) | `76e03984def5fd9258475c7868f708c298b389221801a03d5cc21dfb021a31a5` | none |
| 2026-08-22 | 2026-08-22.1 | Chrome/148.0.7778.280 | R1 (§8) | `76e03984def5fd9258475c7868f708c298b389221801a03d5cc21dfb021a31a5` | none |

Reproduction method: execute Recipe R1 exactly as written (§8) via the documented selectors, then hash the resulting ALL sheet's **raw RGBA pixel buffer** (`getImageData` over the full 384×288 sheet canvas, SHA-256 over `data.buffer`). Pixel bytes are hashed instead of the PNG file because PNG encoders differ across browsers; nearest-neighbor integer rendering makes the pixel buffer deterministic for a given asset set. The hash changes legitimately whenever built-in assets or animations are updated — compare against the row above, not across versions.

## 12. Misc

- Presets/`#preset-random` are instant; there is no undo — re-select parts to revert.
- There is no login and no account. Custom parts never leave the browser; the only way to move them is the backup `.json`.
- Uploaded content must respect copyright; the service disclaims responsibility for user uploads (see the site notice).
- Feedback can be submitted from the page footer.

## 13. Monsters tab

Open `/monsters` directly, or click `a.tab-btn[data-tab="monsters"]`. A standalone bestiary of 17 single-piece creatures (no equipment). Assets lazy-load on first open — wait until `select#mon-select` has options.

| Control | Selector | Behavior |
|---|---|---|
| Monster | `select#mon-select` | Option value = roster index (string). 17 creatures across rig types |
| Color presets | `#mon-presets button.swatch` | Click a swatch to tint (multiply); `data-color` holds its hex |
| Color (free) | `input#mon-color` | Any color; tint is applied to exports too |
| Animation | `select#mon-anim` | `IDLE`/`MOVE`/`ATTACK`/`DAMAGED`/`DEATH` (DEATH is one-shot) |
| Flip | `button#mon-flip` | Toggles facing (`data-facing`) |
| Play/Pause | `button#mon-play` | |
| Export PNG / Sheet | `button#mon-png` / `button#mon-sheet` | Same format as character exports (48×48 frames; sheet = 5 rows) |

## 14. Character Editor tab

Open `/editor` directly, or click `a.tab-btn[data-tab="editor"]`. Draw a custom part pixel-by-pixel or load an existing one and edit it. **Saving uses the same pipeline as upload** — ground-rules (face-window clear, closed-helmet 3-mode derivation, weapon `animType`) apply automatically, and the part lands in the Character tab's `select[data-cat=…]` list.

| Control | Selector | Behavior |
|---|---|---|
| Category | `select#ed-cat` | `Body`/`Hair`/`Helmet`/`Armor`/`Boots`/`Weapon`/`Shield` |
| Load | `select#ed-load` | `-1` = new blank; ≥0 = load that built-in/custom part to edit (Body auto-loads Base 01) |
| Cell (multi-cell parts) | `#ed-cells button` | Body/Armor/Boots have multiple cells; click to switch which cell you paint |
| Tools | `button#ed-pen` / `#ed-eraser` / `#ed-fill` / `#ed-pick` | Right-click always erases |
| Color | `input#ed-color` + `#ed-palette button.swatch` | `#000000` swatch = closed-helmet eye slit |
| Undo/Redo/Clear | `button#ed-undo` / `#ed-redo` / `#ed-clear` | Also Ctrl+Z / Ctrl+Y |
| Animation | `select#ed-anim` | Previews the part on an animated body (facing left, like the Character tab) |
| Form (Helmet/Weapon) | `select#ed-form` | Same options as upload (§7); sets closed/cap or weapon type |
| Adjust | `input#ed-dx` / `#ed-dy` / `#ed-rot` | Reflected in both the edit canvas and the preview (R only for Weapon/Shield) |
| Save | `input#ed-name` + `button#ed-save` | Adds a new custom part (never overwrites) — appears in the Character tab list |

- The edit canvas is shown left-facing to match the Character tab; you draw in the art's native right-facing coordinates and clicks are auto-corrected (a weapon blade drawn toward the front/left is saved pointing +x, per §7).