Vector art has a page of its own (#129)

The feature was documented in pieces - Textures carried the generated
picture, Architecture the modules, Keyboard the tab. Someone who wants
to draw something had to collect it. Vector Art is now one page that
reads from the first click to the finished sprite.

- The new page covers the asset and its canvas, the two tools, what a
  handle does, how rings make a hole, the shape list with its order,
  and the limits that are their own issues (#186, #187, #188, #189).
- Sidebar and Home carry the page, so it is reachable and not only
  linkable.
- Textures and Tiles point at it where they mention a drawn picture,
  instead of describing it a second time in their own words.

Tests:
  node tools/e2e.mjs (all modules) ALL PASSED - 1184 checks
  Development carries that number.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
adminandClaude Opus 5 committed 2026-09-21 05:42:13 +02:00
1 parent c473762f7e
commit 21bea26204
7 files changed
+127 -9

No files matched your search

+1 -1
@@ -13,7 +13,7 @@ npm run check # typecheck + lint + format:check + tests, the gate CI runs
npm run build # writes dist/ and regenerates index.html npm run build # writes dist/ and regenerates index.html
./build.sh # the same under `set -o pipefail`, for callers that pipe the output ./build.sh # the same under `set -o pipefail`, for callers that pipe the output
php tools/make-user.php <name> <password> # an account; every tool signs in (see Accounts) php tools/make-user.php <name> <password> # an account; every tool signs in (see Accounts)
node tools/seed-e2e.mjs && node tools/e2e.mjs # 1179 editor checks, screenshots in /tmp/gm-e2e-*.png node tools/seed-e2e.mjs && node tools/e2e.mjs # 1184 editor checks, screenshots in /tmp/gm-e2e-*.png
GM_STEPS=20 node tools/e2e.mjs # or 1-12, or 6,20 — a subset while working on one step GM_STEPS=20 node tools/e2e.mjs # or 1-12, or 6,20 — a subset while working on one step
./e2e.sh start [GM_STEPS] # the same run detached, see below ./e2e.sh start [GM_STEPS] # the same run detached, see below
./e2e.sh status # live progress, or the last run's result ./e2e.sh status # live progress, or the last run's result
+1
@@ -17,6 +17,7 @@ Live: <https://nockal.com/game-maker/> · Widget playground: <https://nockal.com
| [Colliders](Colliders) | Collider assets, collision events, blocking movement | | [Colliders](Colliders) | Collider assets, collision events, blocking movement |
| [Physics](Physics) | Rigidbodies, the collider material, gravity and units, the fixed step, the contact on `On collision enter` | | [Physics](Physics) | Rigidbodies, the collider material, gravity and units, the fixed step, the contact on `On collision enter` |
| [Textures](Textures) | Getting images in, where they go, tinting, the filter, frames, nine-slice, upload limits | | [Textures](Textures) | Getting images in, where they go, tinting, the filter, frames, nine-slice, upload limits |
| [Vector art](Vector-Art) | The vector art asset, canvas and shapes, the Pen and Select tools, the shape list, how a drawing is worn |
| [Fonts](Fonts) | Uploading a typeface, what a text wears, what the editor falls back to | | [Fonts](Fonts) | Uploading a typeface, what a text wears, what the editor falls back to |
| [Sounds](Sounds) | Uploading a sound, what the asset says, `Play sound` and `Stop sound`, what a pause and a stop do to a voice | | [Sounds](Sounds) | Uploading a sound, what the asset says, `Play sound` and `Stop sound`, what a pause and a stop do to a voice |
| [Animations](Animations) | Animation assets, the Animation tab, how they run | | [Animations](Animations) | Animation assets, the Animation tab, how they run |
+1 -1
@@ -14,4 +14,4 @@ and every tab), the value itself is kept at that precision — fields that are i
milliseconds, the index and count ports of list and text nodes) set `NumberField`'s milliseconds, the index and count ports of list and text nodes) set `NumberField`'s
`decimals: 0` and round to whole numbers · viewport: wheel zooms, `decimals: 0` and round to whole numbers · viewport: wheel zooms,
middle-drag or `Shift`+drag pans, drag on empty space rubber-bands, `Ctrl`/`Shift`-click toggles an middle-drag or `Shift`+drag pans, drag on empty space rubber-bands, `Ctrl`/`Shift`-click toggles an
object in the selection (hierarchy rows too) · tilemap panel: holding `Alt` switches to Pick – a click takes that cell's tile from the active layer – and releasing it brings back the tool before ([Tilemaps](Tilemaps)). object in the selection (hierarchy rows too) · tilemap panel: holding `Alt` switches to Pick – a click takes that cell's tile from the active layer – and releasing it brings back the tool before ([Tilemaps](Tilemaps)) · Vector art tab: `Alt`-click a point to make it round and square again, double-click takes it away, `Esc` ends the line the Pen is drawing, `Delete` removes the selected shape ([Vector art](Vector-Art)).
+6 -4
@@ -171,10 +171,12 @@ looked like before textures existed. So there is no placeholder, no flicker and
Deleting a texture warns how many sprites reference it (a texture is never *placed*, so Deleting a texture warns how many sprites reference it (a texture is never *placed*, so
counting placed instances would report "unused" for a picture a dozen sprites are showing). counting placed instances would report "unused" for a picture a dozen sprites are showing).
**A picture can be drawn instead of uploaded.** Not every picture is a file: an asset may draw ## A picture can be drawn instead of uploaded
its own, and the first one that does is Vector Art (#129). Such an asset is worn exactly like a
texture — it stands in the same picker, a sprite names it in its *Texture* property, a tile can be Not every picture is a file: an asset may draw its own, and the first one that does is
built from it, and a new sprite is measured by its canvas. What the picture path asks of it is two [Vector Art](Vector-Art) (#129). Such an asset is worn exactly like a texture — it stands in the
same picker, a sprite names it in its *Texture* property, a tile can be built from it, and a new
sprite is measured by its canvas. What the picture path asks of it is two
things: the size of its canvas, and a **revision** that changes whenever the drawing changes. The things: the size of its canvas, and a **revision** that changes whenever the drawing changes. The
revision is what makes a changed drawing a new picture, so nothing anywhere has to ask whether a revision is what makes a changed drawing a new picture, so nothing anywhere has to ask whether a
picture is still current; the editor draws the new revision and lets the old one go. picture is still current; the editor draws the new revision and lets the old one go.
+4 -3
@@ -49,9 +49,10 @@ asks for; a picture that does not fit is kept and named, never silently dropped.
**+ → Tile** makes an empty one, of kind *None*. What it holds: **+ → Tile** makes an empty one, of kind *None*. What it holds:
- **Pictures** — one block per animation step, in the order they run. A picture is either a - **Pictures** — one block per animation step, in the order they run. A picture is either a
**texture** (a file that holds nothing but the one block) or a **frame** of a texture (one block **texture** (a file that holds nothing but the one block), a **frame** of a texture (one block
cut out of a sheet that holds many). Cutting them is the [Textures](Textures) tab, by hand or cut out of a sheet that holds many), or a [vector drawing](Vector-Art), which the strip offers
with **Slice…** where the sheet is a regular grid. like any other picture — it has no frames, so it is always the whole canvas. Cutting frames is
the [Textures](Textures) tab, by hand or with **Slice…** where the sheet is a regular grid.
- **Kind** — *None (1 × 1)*, *Floor (2 × 3)* or *Wall (2 × 2)*. Chosen, not guessed: a sheet whose rows alternate - **Kind** — *None (1 × 1)*, *Floor (2 × 3)* or *Wall (2 × 2)*. Chosen, not guessed: a sheet whose rows alternate
between the two shapes (`Outside_A4` in RPG Maker) would be read wrong either way, and the choice between the two shapes (`Outside_A4` in RPG Maker) would be read wrong either way, and the choice
is one click. is one click.
+113
@@ -0,0 +1,113 @@
A **vector art asset** is a picture you draw instead of upload. It holds shapes — rings of points
with bezier handles — and the editor rasterises them into a picture that is worn exactly like a
texture: a sprite names it in its *Texture* row, a tile can be built from it, a shader samples it,
a particle source emits it. Nothing on that path knows it was drawn; see
[Textures](Textures#a-picture-can-be-drawn-instead-of-uploaded) for the contract behind it.
**Making one.** **+ → Vector art** creates the asset; it stands in the assets panel under *Vector
drawings*. A double click on its row opens the **Vector art** tab. Like a font, a vector drawing is
not placed in a scene — it is worn by something that is.
## The canvas
Every drawing has a canvas, **256 × 256** units by default, set in the inspector as the rows
**Canvas width** and **Canvas height** (up to 4096). It is two things at once:
- **The frame.** Whatever a shape reaches past the edge is not in the picture. There is no mask and
no clipping rule to learn — the picture *is* the canvas.
- **The measure.** A new sprite wearing the drawing is that many units wide and high, the same way
a sprite is measured by the pixels of an uploaded image.
The picture is drawn at **twice** the canvas resolution, so zooming in does not turn to mush at
once. It is still a picture, not a curve: zoom in far enough and the edges soften. A drawing that is
meant to be seen big wants a bigger canvas, not a different setting.
## Shapes and rings
A drawing is a list of **shapes**, drawn in order — the last one lies on top. A shape has a fill
colour, an opacity and, if you want one, an **outline** (colour and width).
Each shape is built from one or more **rings**. A ring is a chain of points, and it is either
**closed** or open:
- A **closed** ring is filled and, with an outline, drawn around.
- An **open** ring is outline only. A shape whose rings are all open is a line, not an area.
A second ring **inside** another cuts a **hole** into it, which is how a ring-shaped or a hollow
letter-like form is made. Holes work by containment: a ring inside a ring is a hole, a ring beside
it is a second area of the same shape.
A point is a **corner** or **round**. A round point carries two handles, `in` and `out`, and the
segment between two points is a curve as soon as one of its two ends has a handle — so a corner may
stand right beside a curved side. The two handles are kept apart, not mirrored: pulling one does not
swing the other, which is what lets a curve break at a point.
## The tab
The toolbar holds **Select**, **Pen**, **Grid** and **Fit**; the view pans and zooms like the
Texture tab, and the checkerboard behind the canvas is what is transparent.
**Pen** draws a new shape:
- A click puts a point down; **dragging** while putting it down pulls the two handles out, so a
curve is drawn in one gesture.
- A click on the **first point** closes the ring and ends the shape.
- **Esc** ends the line where it is, leaving an open ring.
**Select** edits what is there:
- Drag a **point**, a **handle**, or the **whole shape**.
- A click **on a segment** puts a new point there. On a curved segment the curve is *split*, not
cut: both halves together draw exactly what was drawn before.
- A **double click** on a point takes it away.
- **Alt** on a point makes it round, and a round one square again.
- **Delete** removes the selected shape.
**Grid** snaps to 8 units while it is on; it is off by default. **Fit** brings the whole canvas back
into view.
One gesture is **one undo step** — dragging a point across the canvas is a single entry in the undo
history, not one per mouse move. While a gesture runs, the drawing is redrawn once per frame rather
than once per mouse event, which is what keeps a fast drag from falling behind the pointer.
## The inspector
Selecting a shape — in the tab or in the list — gives it its own rows:
- **SHAPES** lists every shape of the asset, each row painted with the shape itself, the whole
canvas scaled into the swatch, so a row also says *where* on the canvas its shape sits. The
**topmost row is drawn last**, the way a layer list reads.
- **Forward / To front / Backward / To back** move the selected shape through that order, and a row
can be **dragged** to another place in the list.
- **VECTOR SHAPE** carries Name, Fill, Opacity and an **Outline** switch; with the switch on, Line
colour and Line width appear. The switch *gives and takes away* the outline rather than hiding it,
and a width of zero is no outline, so the row stops at 0.1.
- **Delete shape** and **Add shape** sit below.
A dragged colour or opacity slider is one undo step, not one per pixel of the drag. Selecting the
**asset** instead of a shape shows the same list under the asset's own rows, with nothing selected.
## Wearing it
A sprite's **Texture** row offers every picture of the project: the uploaded textures, their frames,
and the drawings, sorted by name. Picking a drawing there is all there is to it — the sprite shows
it, and every change in the tab reaches it by itself, because a changed drawing is a new picture.
Two differences to an uploaded picture, both from the picture path: a drawing has **no frames**
(frames are rectangles cut out of a sheet, and there is no sheet), and it is **not stored** as an
image — the project file holds the shapes, and the picture is rasterised again after a reload.
## Known limits
- **A fill is one colour.** Gradients and texture fills are #186.
- **No boolean operations** — no union, no subtraction. A hole is a ring inside a ring; anything
beyond that is #187.
- **A collider does not follow the drawing.** A sprite wearing vector art collides with its box or
with a shape you give it by hand, as with any picture; taking the outline as a collider shape is
#188.
- **No SVG import** (#189), **no groups or layers** inside an asset
(#190).
- The shapes are edited **in the inspector**, not in a panel of their own like the tilemap's
(#191).
- **No runtime nodes** for drawing at play time. A sprite can still swap its drawing through the
ordinary `texture` property.
+1
@@ -8,6 +8,7 @@
- [Colliders](Colliders) - [Colliders](Colliders)
- [Physics](Physics) - [Physics](Physics)
- [Textures](Textures) - [Textures](Textures)
- [Vector art](Vector-Art)
- [Fonts](Fonts) - [Fonts](Fonts)
- [Sounds](Sounds) - [Sounds](Sounds)
- [Animations](Animations) - [Animations](Animations)