Neues Asset: Vector Art #129

Closed
opened 2026-09-17 20:55:18 +02:00 by admin · 7 comments

Vector Art mit eigenem editor (tab) - dort soll man Formen mit farbe und kurven erstellen können. Punkte hinzufügen, bezier kurven. das kann man dann bei einem Sprite auswählen können wie Texture oder Texture-Frame

Vector Art mit eigenem editor (tab) - dort soll man Formen mit farbe und kurven erstellen können. Punkte hinzufügen, bezier kurven. das kann man dann bei einem Sprite auswählen können wie Texture oder Texture-Frame
admin added this to the Development project 2026-09-17 20:56:29 +02:00
admin moved this to Next in Development on 2026-09-20 19:25:38 +02:00
Owner

Fragen zur Planung

Ich habe den Code sondiert (Bildpfad, Sprite-Rendering, Asset-Modell, der vorhandene Polygon-Editor) und dabei ein paar Entscheidungen gefunden, die vor dem Bauen fallen müssen. Zu jeder mein Vorschlag — es reicht, wenn du „passt" schreibst oder die Nummern nennst, die du anders willst.

1. Rastern oder als echte Geometrie zeichnen?

Das ist die Grundsatzfrage, alles andere hängt daran. Heute ist jedes Sprite ein Mesh mit Quad-Geometrie, das eine Texture trägt (viewport/symbols.ts), und daran hängen Shader (viewport/shaders.ts), Partikel, Nine-Slice und die Tile-Bilder — alle erwarten eine Textur.

  • A (mein Vorschlag): Vector Art wird zu einer Textur gerastert. Das Asset wird in eine RenderTexture gezeichnet und wie eine hochgeladene Datei aus der TextureLibrary geholt; neu gerastert wird beim Ändern des Assets. Vorteil: alles Bestehende funktioniert sofort — Shader, Partikel, Tiles, Nine-Slice, Animation, das Seitenverhältnis. Nachteil: beim starken Hineinzoomen ist es ein Bild, keine Kurve mehr (mildern lässt sich das über eine Rasterauflösung, die größer ist als die Sprite-Größe).
  • B: Eigener Zeichenweg (Mesh aus triangulierten Pfaden). Bei jedem Zoom gestochen scharf, aber Shader, Partikel, Tiles und Nine-Slice fallen erstmal weg bzw. sind je ein eigener Umbau.

2. Wo darf Vector Art überall stehen?

Ein Bild ist heute eine Id — Textur-Asset oder Frame — und alles geht durch resolvePicture(). Wenn Vector Art dort mitläuft, taucht es ohne Mehraufwand überall auf, wo heute ein Bild steht: Sprite, Tile (und damit Tilemaps), Partikelquelle, der Bild-Picker.

Vorschlag: überall, also ein Ausgang statt vier. Willst du es bewusst nur am Sprite haben?

3. Wie groß ist ein Vector-Art-Asset?

Eine Textur hat eine Pixelgröße, daraus nimmt ein neu gesetztes Sprite seine Breite und Höhe (properties/aspect.ts). Kurven haben keine.

Vorschlag: Das Asset hat eine eigene Leinwandgröße (Vorgabe 256 × 256, im Inspector änderbar). Sie ist der Bezug für Größe und Seitenverhältnis, und der Editor zeigt sie als Rahmen. Zusatzfrage: Wird an diesem Rahmen abgeschnitten oder darf eine Form darüber hinausragen? Mein Vorschlag: abschneiden, sonst ist die Größe des Sprites eine Lüge.

4. Was genau ist eine Form?

Vorschlag: ein geschlossener Pfad aus Punkten, wobei jeder Punkt entweder eine Ecke (gerade Kante) oder glatt mit zwei Bezier-Griffen ist (kubisch) — das übliche Pen-Tool-Modell. Dazu je Form: Füllfarbe + Deckkraft, optional Kontur (Farbe, Breite). Offene Pfade (nur Strich, keine Füllung) — willst du die auch?

5. Wie reich darf die Füllung sein?

Vorschlag für Runde 1: einfarbig plus Deckkraft. Verläufe (linear/radial) und eine Füllung mit einer Textur wären je ein eigenes Issue — sagenswert ist mir vor allem, ob du Verläufe als Teil von #129 erwartest.

6. Mehrere Formen, Löcher, Gruppen?

Vorschlag: eine flache Liste von Formen mit Reihenfolge (oben/unten verschieben), keine Gruppen und keine booleschen Operationen (Vereinigen/Abziehen). Löcher bekommt eine Form dadurch, dass sie mehrere Ringe haben darf (Even-Odd) — oder reicht dir fürs Erste, ein Loch durch eine hintergrundfarbene Form darüber zu bauen? Mein Vorschlag: Ringe ja, boolesche Operationen nein.

7. Werkzeuge im Tab

Vorschlag: Zoomen/Schieben wie im Texture-Tab; Stift (neue Form Punkt für Punkt), Auswahl (Punkt ziehen, Griff ziehen, ganze Form ziehen), Punkt auf einer Kante einfügen, Doppelklick entfernt ihn — die Regeln dafür gibt es schon als reine Funktionen in editor/polygonEdit.ts (#141/#151), die würde ich um die Bezier-Griffe erweitern statt etwas Zweites zu bauen. Punkte am Raster einrasten: einschaltbar, standardmäßig aus.

8. Wo werden Farbe und Kontur eingestellt?

Vorschlag: im Inspector rechts, wenn eine Form ausgewählt ist (wie sonst die Eigenschaften eines Objekts), plus eine Formenliste im Tab. Alternative wäre eine Werkzeugleiste im Tab selbst.

9. Kollision

Analog zu #161 („Frame outline" als Collider-Form) könnte ein Collider die Umrisse einer Vector-Art-Form übernehmen.

Vorschlag: nicht in dieser Runde, aber als eigenes Issue notieren — die Formen liegen dann ja schon als Polygone vor.

10. Laufzeit

Vorschlag: keine Nodes zum Ändern von Farben oder Punkten im Lauf, und keine Animation der Punkte auf der Timeline. Beides später, wenn es gebraucht wird. Ein Sprite kann seine Vector Art zur Laufzeit wechseln, weil texture schon eine Eigenschaft ist — das fällt ohne Arbeit ab.

11. SVG-Import?

Vorschlag: nein, eigenes Issue. Ein SVG-Parser ist ein Thema für sich (Transformationen, Gruppen, Verläufe, Text).

12. Verhältnis zu #130 (Pixel Editor)

Beide Issues bringen ein Bild, das keine hochgeladene Datei ist. Der Bildpfad (resolvePicture, der Picker, die TextureLibrary) muss das genau einmal lernen.

Vorschlag: Ich baue diese Stelle in #129 so, dass #130 nur noch ein zweiter Erzeuger ist, und schreibe den Vertrag ins Wiki. Wenn du #130 zeitnah willst, wäre auch denkbar, die gemeinsame Grundlage vorzuziehen — sag Bescheid, ich richte mich danach.


Offen für mich ist vor allem 1, 3 und 6 — der Rest lässt sich auch später noch drehen.

## Fragen zur Planung Ich habe den Code sondiert (Bildpfad, Sprite-Rendering, Asset-Modell, der vorhandene Polygon-Editor) und dabei ein paar Entscheidungen gefunden, die vor dem Bauen fallen müssen. Zu jeder mein Vorschlag — es reicht, wenn du „passt" schreibst oder die Nummern nennst, die du anders willst. ### 1. Rastern oder als echte Geometrie zeichnen? Das ist die Grundsatzfrage, alles andere hängt daran. Heute ist jedes Sprite ein **Mesh mit Quad-Geometrie, das eine `Texture` trägt** (`viewport/symbols.ts`), und daran hängen Shader (`viewport/shaders.ts`), Partikel, Nine-Slice und die Tile-Bilder — alle erwarten eine Textur. - **A (mein Vorschlag): Vector Art wird zu einer Textur gerastert.** Das Asset wird in eine `RenderTexture` gezeichnet und wie eine hochgeladene Datei aus der `TextureLibrary` geholt; neu gerastert wird beim Ändern des Assets. Vorteil: **alles Bestehende funktioniert sofort** — Shader, Partikel, Tiles, Nine-Slice, Animation, das Seitenverhältnis. Nachteil: beim starken Hineinzoomen ist es ein Bild, keine Kurve mehr (mildern lässt sich das über eine Rasterauflösung, die größer ist als die Sprite-Größe). - **B: Eigener Zeichenweg (Mesh aus triangulierten Pfaden).** Bei jedem Zoom gestochen scharf, aber Shader, Partikel, Tiles und Nine-Slice fallen erstmal weg bzw. sind je ein eigener Umbau. ### 2. Wo darf Vector Art überall stehen? Ein Bild ist heute **eine Id** — Textur-Asset oder Frame — und alles geht durch `resolvePicture()`. Wenn Vector Art dort mitläuft, taucht es ohne Mehraufwand überall auf, wo heute ein Bild steht: Sprite, **Tile** (und damit Tilemaps), **Partikelquelle**, der Bild-Picker. **Vorschlag:** überall, also ein Ausgang statt vier. Willst du es bewusst nur am Sprite haben? ### 3. Wie groß *ist* ein Vector-Art-Asset? Eine Textur hat eine Pixelgröße, daraus nimmt ein neu gesetztes Sprite seine Breite und Höhe (`properties/aspect.ts`). Kurven haben keine. **Vorschlag:** Das Asset hat eine eigene **Leinwandgröße** (Vorgabe 256 × 256, im Inspector änderbar). Sie ist der Bezug für Größe und Seitenverhältnis, und der Editor zeigt sie als Rahmen. **Zusatzfrage:** Wird an diesem Rahmen **abgeschnitten** oder darf eine Form darüber hinausragen? Mein Vorschlag: abschneiden, sonst ist die Größe des Sprites eine Lüge. ### 4. Was genau ist eine Form? **Vorschlag:** ein **geschlossener Pfad** aus Punkten, wobei jeder Punkt entweder eine **Ecke** (gerade Kante) oder **glatt** mit zwei Bezier-Griffen ist (kubisch) — das übliche Pen-Tool-Modell. Dazu je Form: **Füllfarbe + Deckkraft**, optional **Kontur** (Farbe, Breite). Offene Pfade (nur Strich, keine Füllung) — willst du die auch? ### 5. Wie reich darf die Füllung sein? **Vorschlag für Runde 1:** einfarbig plus Deckkraft. **Verläufe** (linear/radial) und eine Füllung mit einer Textur wären je ein eigenes Issue — sagenswert ist mir vor allem, ob du Verläufe als Teil von #129 erwartest. ### 6. Mehrere Formen, Löcher, Gruppen? **Vorschlag:** eine flache Liste von Formen mit Reihenfolge (oben/unten verschieben), **keine Gruppen und keine booleschen Operationen** (Vereinigen/Abziehen). **Löcher** bekommt eine Form dadurch, dass sie mehrere Ringe haben darf (Even-Odd) — oder reicht dir fürs Erste, ein Loch durch eine hintergrundfarbene Form darüber zu bauen? Mein Vorschlag: Ringe ja, boolesche Operationen nein. ### 7. Werkzeuge im Tab **Vorschlag:** Zoomen/Schieben wie im Texture-Tab; **Stift** (neue Form Punkt für Punkt), **Auswahl** (Punkt ziehen, Griff ziehen, ganze Form ziehen), Punkt auf einer Kante einfügen, Doppelklick entfernt ihn — die Regeln dafür gibt es schon als reine Funktionen in `editor/polygonEdit.ts` (#141/#151), die würde ich um die Bezier-Griffe erweitern statt etwas Zweites zu bauen. Punkte am Raster einrasten: einschaltbar, standardmäßig aus. ### 8. Wo werden Farbe und Kontur eingestellt? **Vorschlag:** im **Inspector** rechts, wenn eine Form ausgewählt ist (wie sonst die Eigenschaften eines Objekts), plus eine Formenliste im Tab. Alternative wäre eine Werkzeugleiste im Tab selbst. ### 9. Kollision Analog zu #161 („Frame outline" als Collider-Form) könnte ein Collider die Umrisse einer Vector-Art-Form übernehmen. **Vorschlag:** **nicht** in dieser Runde, aber als eigenes Issue notieren — die Formen liegen dann ja schon als Polygone vor. ### 10. Laufzeit **Vorschlag:** keine Nodes zum Ändern von Farben oder Punkten im Lauf, und keine Animation der Punkte auf der Timeline. Beides später, wenn es gebraucht wird. Ein Sprite kann seine Vector Art zur Laufzeit wechseln, weil `texture` schon eine Eigenschaft ist — das fällt ohne Arbeit ab. ### 11. SVG-Import? **Vorschlag:** nein, eigenes Issue. Ein SVG-Parser ist ein Thema für sich (Transformationen, Gruppen, Verläufe, Text). ### 12. Verhältnis zu #130 (Pixel Editor) Beide Issues bringen ein **Bild, das keine hochgeladene Datei ist**. Der Bildpfad (`resolvePicture`, der Picker, die `TextureLibrary`) muss das genau einmal lernen. **Vorschlag:** Ich baue diese Stelle in #129 so, dass #130 nur noch ein zweiter Erzeuger ist, und schreibe den Vertrag ins Wiki. Wenn du #130 zeitnah willst, wäre auch denkbar, die gemeinsame Grundlage vorzuziehen — sag Bescheid, ich richte mich danach. --- **Offen für mich ist vor allem 1, 3 und 6** — der Rest lässt sich auch später noch drehen.
Author
  1. definitiv rastern, muss behandelt werden wie eine texture
  2. passt, überall
  3. passt, auch das mit dem abschneiden
  4. passt, alles wenn geht
  5. vorschlag passt, erstmal nur solide füllung, verlauf ist folge issue
  6. passt, aber auch boolsche operationen als folge issue
  7. passt, poly bearbeitung wiederverwenden und erweitern mit bezier handles
  8. passt, inspector - später vielleicht eigenes panel wie bei tilemap
  9. passt, eigenes issue
  10. dein vorschlag, laufzeit brauchen wir vorerst garnicht
  11. passt, eigens issue
  12. passt, gemeinsame grundlage vorziehen

weiters: reihenfolge von formen muss man auch festlegen können (eins nach vorne bringen, ganz nach vorne bringen, und auch eins nach hinten bzw. ganz nach hinten) - vielleicht wäre da eine hierarchie im inspektor nicht schlecht, mit vorschau vom shape. weiteres folge issue wären dann sogar gruppen bzw. layers

1. definitiv rastern, muss behandelt werden wie eine texture 2. passt, überall 3. passt, auch das mit dem abschneiden 4. passt, alles wenn geht 5. vorschlag passt, erstmal nur solide füllung, verlauf ist folge issue 6. passt, aber auch boolsche operationen als folge issue 7. passt, poly bearbeitung wiederverwenden und erweitern mit bezier handles 8. passt, inspector - später vielleicht eigenes panel wie bei tilemap 9. passt, eigenes issue 10. dein vorschlag, laufzeit brauchen wir vorerst garnicht 11. passt, eigens issue 12. passt, gemeinsame grundlage vorziehen weiters: reihenfolge von formen muss man auch festlegen können (eins nach vorne bringen, ganz nach vorne bringen, und auch eins nach hinten bzw. ganz nach hinten) - vielleicht wäre da eine hierarchie im inspektor nicht schlecht, mit vorschau vom shape. weiteres folge issue wären dann sogar gruppen bzw. layers
Owner

Plan

Aus deinen Antworten (Kommentar oben) folgt der Zuschnitt. Gebaut ist noch nichts — das hier ist der Stand zum Durchsehen.

Die tragende Entscheidung

Rastern (1). Damit wird Vector Art behandelt wie eine Textur, läuft durch resolvePicture() und ist ohne Mehrarbeit überall (2) — Sprite, Tile, Tilemap, Partikelquelle, Picker. Shader, Nine-Slice, Animation und das Seitenverhältnis funktionieren unverändert, weil sie weiterhin eine Texture sehen.

Die einzige Stelle, die ich im Plan offen gelassen hatte, ist die Überabtastung: gerastert wird in 2× der Leinwandgröße, damit starkes Hineinzoomen nicht sofort matscht. Das ist so festgelegt, solange du nichts anderes sagst.

Vorgezogen: #185 — Erzeugte Bilder im Bildpfad

Zu 12 („gemeinsame Grundlage vorziehen"). Heute ist TextureRef.file ein Dateiname auf dem Server (isTextureFile() prüft Hash + Endung, src/core/model/texture.ts:98), und TextureLibrary lädt ihn per HTTP. Ein erzeugtes Bild hat keine Datei.

#185 lehrt dem Bildpfad genau einmal, dass ein Bild auch erzeugt sein darf: Schlüssel gen:<assetId>@<rev>, ein Register von Erzeugern statt des Servers, Neuzeichnen über einen Revisionszähler, Leinwandgröße als sourceWidth/Height, Picker und Nutzungssuche mit. #130 (Pixel Editor) ist danach nur noch ein zweiter Erzeuger. Das Issue wird eigens gebaut, committet und geschlossen, bevor #129 anfängt.

#129 selbst, in fünf Etappen

  1. Modell — neuer Asset-Typ vectorart in ASSET_TYPES (nicht platzierbar): Leinwand w/h, Liste von Formen. Eine Form = ein oder mehrere Ringe (Even-Odd, also Löcher — 6), je Ring Punkte mit optionalen Bezier-Griffen in/out, closed ja/nein (offener Pfad = nur Kontur — 4), dazu Füllfarbe + Deckkraft und optional Kontur (Farbe, Breite — 5). Serialisierung, Validierung, Undo-Commands.
  2. Rasterung — reine Funktion Pfad → Zeichenbefehle (unit-testbar, ohne GPU), gezeichnet in eine RenderTexture in 2× Leinwandgröße, an der Leinwand abgeschnitten (3).
  3. Tab — Zoom/Pan wie im Texture-Tab; Stift (neue Form Punkt für Punkt), Auswahl (Punkt, Griff, ganze Form ziehen), Punkt auf einer Kante einfügen, Doppelklick entfernt ihn. editor/polygonEdit.ts wird um die Bezier-Griffe erweitert statt etwas Zweites zu bauen (7) — dieselben Regeln gelten dann auch für Collider-Polygone und Frame-Umrisse. Raster-Einrasten schaltbar, Vorgabe aus.
  4. Inspector (8) — Formenliste mit Vorschau und der Reihenfolge aus deinem Nachtrag: eins nach vorne, ganz nach vorne, eins nach hinten, ganz nach hinten, dazu Ziehen in der Liste. Darunter Farbe, Deckkraft, Kontur; am Asset die Leinwandgröße.
  5. Tests und Doku — Unit-Tests für Modell, Rasterbefehle und die erweiterten Punkt-Regeln; ein e2e-Modul (Form zeichnen, Farbe setzen, auf ein Sprite legen, Reihenfolge ändern); Wiki-Seite Vector Art plus Nachträge in Architecture, Textures, Tilemaps.

Folge-Issues (angelegt)

Nr. Thema aus Frage
#186 Verläufe und Texturfüllung 5
#187 boolesche Operationen 6
#188 Collider-Form „Vector art outline" 9
#189 SVG-Import 11
#190 Gruppen und Layers Nachtrag
#191 eigenes Panel statt Inspector 8

Laufzeit-Nodes lege ich nicht an (10, „brauchen wir vorerst gar nicht"). Dass ein Sprite seine Vector Art im Lauf wechseln kann, fällt über die Eigenschaft texture ohnehin ab.

Zwei Anmerkungen

  • Das ist groß. #185 wird eigens gebaut und geschlossen; #129 danach in einem Zug, weil ein halbes Asset (Modell ohne Tab) nichts nützt.
  • Reihenfolge: #185 → #129 → der Rest nach Bedarf.
## Plan Aus deinen Antworten (Kommentar oben) folgt der Zuschnitt. Gebaut ist noch nichts — das hier ist der Stand zum Durchsehen. ### Die tragende Entscheidung **Rastern** (1). Damit wird Vector Art behandelt wie eine Textur, läuft durch `resolvePicture()` und ist ohne Mehrarbeit **überall** (2) — Sprite, Tile, Tilemap, Partikelquelle, Picker. Shader, Nine-Slice, Animation und das Seitenverhältnis funktionieren unverändert, weil sie weiterhin eine `Texture` sehen. Die einzige Stelle, die ich im Plan offen gelassen hatte, ist die **Überabtastung**: gerastert wird in **2× der Leinwandgröße**, damit starkes Hineinzoomen nicht sofort matscht. Das ist so festgelegt, solange du nichts anderes sagst. ### Vorgezogen: #185 — Erzeugte Bilder im Bildpfad Zu 12 („gemeinsame Grundlage vorziehen"). Heute ist `TextureRef.file` ein Datei**name** auf dem Server (`isTextureFile()` prüft Hash + Endung, `src/core/model/texture.ts:98`), und `TextureLibrary` lädt ihn per HTTP. Ein erzeugtes Bild hat keine Datei. **#185** lehrt dem Bildpfad genau einmal, dass ein Bild auch erzeugt sein darf: Schlüssel `gen:<assetId>@<rev>`, ein Register von Erzeugern statt des Servers, Neuzeichnen über einen Revisionszähler, Leinwandgröße als `sourceWidth/Height`, Picker und Nutzungssuche mit. #130 (Pixel Editor) ist danach nur noch ein zweiter Erzeuger. Das Issue wird eigens gebaut, committet und geschlossen, bevor #129 anfängt. ### #129 selbst, in fünf Etappen 1. **Modell** — neuer Asset-Typ `vectorart` in `ASSET_TYPES` (nicht platzierbar): Leinwand w/h, Liste von Formen. Eine Form = ein oder mehrere **Ringe** (Even-Odd, also Löcher — 6), je Ring Punkte mit optionalen Bezier-Griffen `in`/`out`, `closed` ja/nein (offener Pfad = nur Kontur — 4), dazu Füllfarbe + Deckkraft und optional Kontur (Farbe, Breite — 5). Serialisierung, Validierung, Undo-Commands. 2. **Rasterung** — reine Funktion Pfad → Zeichenbefehle (unit-testbar, ohne GPU), gezeichnet in eine `RenderTexture` in 2× Leinwandgröße, **an der Leinwand abgeschnitten** (3). 3. **Tab** — Zoom/Pan wie im Texture-Tab; **Stift** (neue Form Punkt für Punkt), **Auswahl** (Punkt, Griff, ganze Form ziehen), Punkt auf einer Kante einfügen, Doppelklick entfernt ihn. `editor/polygonEdit.ts` wird um die Bezier-Griffe **erweitert** statt etwas Zweites zu bauen (7) — dieselben Regeln gelten dann auch für Collider-Polygone und Frame-Umrisse. Raster-Einrasten schaltbar, Vorgabe aus. 4. **Inspector** (8) — **Formenliste mit Vorschau** und der Reihenfolge aus deinem Nachtrag: eins nach vorne, ganz nach vorne, eins nach hinten, ganz nach hinten, dazu Ziehen in der Liste. Darunter Farbe, Deckkraft, Kontur; am Asset die Leinwandgröße. 5. **Tests und Doku** — Unit-Tests für Modell, Rasterbefehle und die erweiterten Punkt-Regeln; ein e2e-Modul (Form zeichnen, Farbe setzen, auf ein Sprite legen, Reihenfolge ändern); Wiki-Seite *Vector Art* plus Nachträge in Architecture, Textures, Tilemaps. ### Folge-Issues (angelegt) | Nr. | Thema | aus Frage | |---|---|---| | #186 | Verläufe und Texturfüllung | 5 | | #187 | boolesche Operationen | 6 | | #188 | Collider-Form „Vector art outline" | 9 | | #189 | SVG-Import | 11 | | #190 | Gruppen und Layers | Nachtrag | | #191 | eigenes Panel statt Inspector | 8 | **Laufzeit-Nodes lege ich nicht an** (10, „brauchen wir vorerst gar nicht"). Dass ein Sprite seine Vector Art im Lauf wechseln kann, fällt über die Eigenschaft `texture` ohnehin ab. ### Zwei Anmerkungen - **Das ist groß.** #185 wird eigens gebaut und geschlossen; #129 danach in einem Zug, weil ein halbes Asset (Modell ohne Tab) nichts nützt. - **Reihenfolge:** #185 → #129 → der Rest nach Bedarf.
meleki referenced this issue from a commit 2026-09-20 20:52:02 +02:00
A picture in the editor may now be drawn instead of uploaded, which is the piece #129 (Vector art) and #130 (Pixel editor) both need and neither could build alone: TextureRef.file has been a file name on the server, isTextureFile() checking a sha256 hash plus an extension and TextureLibrary fetching it over HTTP, so an asset that paints its own image had nowhere to put itself - the key now also takes the form gen:<assetId>@<revision>, which an uploaded file can never look like, and everything that only passes a ref along (sprite, tile, tilemap mesh, particle source, shaders, aspect ratio) is untouched because it still sees a Texture; the revision is a string, not a counter, which is the one deviation from the plan in the issue and costs nothing - gen:a1@7 and gen:a1@c0ffee go through the same door, so a generator may fingerprint its own content instead of relying on every command to remember to bump a number; two registries keyed by asset type as a plain string, so a plugin may bring its own type - registerPictureSource(type, read) in the new core/model/generated.ts tells the model canvas size and revision and resolvePicture() builds the ref from it (no rect, no slice, smooth from props.filter like a texture), and registerPictureGenerator(type, draw) in the new viewport/generatedPictures.ts hands out the finished texture, projectPictureFactory(() => state.project) tying the two together per session in App.ts; invalidation is a rule instead of guesswork at twenty call sites: GeneratedPictures<T> holds one picture per asset keyed on the revision and frees the old one as soon as the new is drawn, TextureLibrary destroying it and dropping the crops cut from it, while a generator that cannot draw yet is not remembered but asked again next time - the class imports Pixi as a type only, so the whole bookkeeping is measurable without a GPU; carried along are the picture picker (a drawn asset sits among the textures by name, without frames), the delete warning (counts tiles and animations for it too) and the asset context menu, where "Create sprite from picture" now covers it while "Create animation from texture" stays texture-only because it needs frames; one finding while probing - crop() could not have cut a generated texture at all, a RenderTexture having no source.resource to draw back from and drawImage running onto null, so it is handed out whole, a drawn picture having no sheet whose neighbouring row could bleed in; 18 new unit tests, 10 in tests/core/generated-picture.test.ts (building and splitting the key, an uploaded file is not one, no registry means no picture, resolving to canvas size, a new revision is a new key, a canvas without a size, the filter, the sprite size, picker order) and 8 in tests/viewport/generated-pictures.test.ts (drawing once, freeing the old revision and only for its own asset, null is not remembered, no key means nothing drawn, a session change gives everything back, peek), and three counter-checks hold: dropping generated assets from resolvePicture fails 5 of the 10 core tests, not freeing the old revision fails 1 viewport test, and narrowing the picker back to textures fails 3; no e2e check and the check sum stays 1157, because without a generator the groundwork shows nothing in the browser - that evidence comes with #129 (#185)
meleki referenced this issue from a commit 2026-09-20 20:52:02 +02:00
The asset type "vector art" exists as a model, which is stage 1 of #129 and shows nothing in the browser yet on purpose: a vectorart asset is not placeable, sits in the + menu and in the asset panel with its own icon (a curve between two points) and has no tab, so it behaves like a font - selectable, its rows in the inspector, a double click leading nowhere - because the tab is stage 3 and half an asset would only be in the way; the data model in Asset.ts is a shape built from rings (even-odd, so a shape may have holes, answer 6 in the issue), a ring being points with optional bezier handles in/out stored as an offset from the point and a closed flag (an open path is stroke only, answer 4), plus fill colour, opacity and an optional stroke (answer 5) - the two handles are kept apart instead of mirrored, because what "smooth" means is the editor's business and not the model's; canvas width, height and the filter are properties, so they get an inspector row and undo like any other, while the shapes are intrinsic data like the frames of a texture; the revision that #185 hands to the picture path is a fingerprint of what is drawn, not a counter, which is exactly why that key took a string token - no command can forget to bump it - and the name of a shape is deliberately left out of it, renaming changing no pixel; six undo commands in the new vectorArtCommands.ts (add a shape numbered in order, remove one which comes back to its old place - unlike a tilemap layer the last one may go too -, rename, move, write the rings as one step per gesture with the list copied, and set the style where stroke: null takes it away) and the pure function shapeOrderTarget for the four buttons of the planned inspector list (one forward, to front, one back, to back), which also serves dragging in that list; loading from a project file is strict - a point without numbers falls away, a broken handle leaves a corner behind, duplicate shape ids fall away like duplicate layer ids, a stroke of width 0 is no stroke - while a ring with a single point, which is what someone is drawing right now, stays; three findings on the way: JSON.stringify as the fingerprint was wrong because the same drawing came back from a project file as {points, closed} and was built as {closed, points}, so a reload would have rasterized the same picture under a new key (the fingerprint writes its fields in a fixed order now and a test measures that case), toNumber in serialize.ts turns any nonsense into 0 so the Number.isFinite checks ran into nothing and {x: 'far'} became the left edge instead of vanishing (shapes read numbers strictly via asFinite, 0 being a place on the canvas here), and "Vector art" would have been both a menu entry and the panel heading, which an existing test catches because a click by text would hit the heading - the group is called "Vector drawings" now while the menu entry stays "Vector art"; 20 new unit tests in tests/core/vectorart.test.ts and three counter-checks hold: the fingerprint back on JSON.stringify fails 2, reading the numbers loosely again fails 4, and not copying the ring list fails 1; no e2e check and the check sum stays 1157, only module 04 counting 19 asset types instead of 18 (#129)
meleki referenced this issue from a commit 2026-09-20 20:52:02 +02:00
A vector art asset is a picture now, which is stage 2 of #129 and the first stage that shows something in the browser: the asset sits in the picture picker, a sprite carries it in its Texture row and what the sprite shows has been rasterized from the shapes, the generator of #185 being registered for the type vectorart - installVectorArtPictures(app.renderer) hooks the source (core) and the generator (viewport) in once per page from App.ts, by a call and not by a module side effect, so a test may install it or leave it out; the rule lives in core and the painting in viewport - the new core/model/vectorPath.ts turns a shape into plain drawing commands (move/line/curve/close), closed rings being filled while all rings are stroked, a segment staying a line as long as neither of its ends carries a handle - a single handle is enough for a curve, the missing control point sitting on its own end, so a corner may stand next to a round side - and the way back to the first point is only drawn when it is curved, close doing it otherwise; holes go through one door only, Pixi recognising them from a GraphicsPath with checkForHoles, which tests containment rather than even-odd depth, which is enough for a ring inside a ring while without the flag the inner ring simply paints over the outer one (measured, see the counter-checks); the oversampling is the 2x from the plan - RenderTexture.create({resolution: 2}) puts 512 px on the GPU for a 256 canvas while texture.width stays the canvas size, which is the measure the sprite hangs on, and the render texture is the clip itself, so what juts out over the canvas is not in it and needs no mask; drawn pictures get the same sampler state as uploaded ones, setPictureFactory wrapping the factory for it, mirroring keep(); two measuring traps cost me runs - __gm.objectRect(id) gives the centre of the object and not its corner, so the first pass read four pixels next to the sprite and reported background while the rasterizer was correct, and the grid lines of the scene cross exactly in the centre point, so the hole check read #363940 and failed for the right reason, measuring beside it now; renderer.render() runs inside the picture request, which is safe here because every caller asks during its own widget redraw and the frame is drawn on the ticker afterwards, a nested render inside a running render pass would not be - that warning stands in the module head and in the notes; new module 47 with 8 own checks in its own lane (the picture on the GPU at double resolution measuring the canvas, the sprite showing the fill, the inner ring being a hole with the scene shining through, the later shape on top, the stroke running along the ring, a changed drawing getting a new key with the old one freed, and the sprite showing the new colour), the shapes being put into the model by mutate because the tab that draws them is stage 3, and 21 new unit tests in tests/core/vector-path.test.ts; three counter-checks hold: oversampling back to 1 fails the GPU check, checkForHoles off fails the hole check, and not drawing the curved way back fails a unit test; the full suite ran locally instead of only in CI because the change sits in TextureLibrary and touches every picture: ALL PASSED - 1165 checks, measured and carried into README and the wiki (#129)
Owner

Etappen 1 und 2 sind gebaut und gepusht. Die Grundlage #185 ist vorher geschlossen. Das Issue bleibt offen — es fehlen Etappe 3 (Tab), 4 (Inspector) und 5 (Tests/Doku zum Zeichnen).

Etappe 1 — das Modell (bdb3f06)

Der Asset-Typ vectorart existiert, nicht platzierbar, im +-Menü und im Asset-Panel mit eigenem Symbol (eine Kurve zwischen zwei Punkten). Einen Tab hat er noch nicht — er verhält sich wie eine Font: auswählbar, Inspector zeigt seine Zeilen, Doppelklick führt nirgendwohin. Ein halbes Asset wäre nur im Weg, der Tab ist Etappe 3.

Das Datenmodell in Asset.ts: eine Form = mehrere Ringe (Even-Odd, also Löcher — Antwort 6), je Ring Punkte mit optionalen Bezier-Griffen in/out als Offset vom Punkt und closed ja/nein (offen = nur Kontur — Antwort 4), dazu Füllfarbe, Deckkraft und optional Kontur (Antwort 5). Die beiden Griffe liegen getrennt statt gespiegelt: was „glatt" heißt, ist Sache des Editors, nicht des Modells. Leinwandbreite, -höhe und Filter sind Properties (Inspector-Zeile und Undo wie jede andere), die Formen sind intrinsische Daten wie die Frames einer Textur.

Die Revision, die an #185 geht, ist ein Fingerabdruck des Gezeichneten, kein Zähler — genau deshalb nahm der Schlüssel dort einen String-Token: kein Command kann vergessen, sie hochzuzählen. Der Name einer Form steckt bewusst nicht drin, Umbenennen ändert kein Pixel.

Sechs Undo-Commands in vectorArtCommands.ts (Form hinzufügen, entfernen — sie kommt an ihre alte Stelle zurück, und anders als eine Tilemap-Ebene darf auch die letzte gehen —, umbenennen, verschieben, Ringe schreiben als ein Schritt pro Geste mit kopierter Liste, Stil setzen, wobei stroke: null die Kontur wegnimmt) plus die reine Funktion shapeOrderTarget für die vier Knöpfe der geplanten Inspector-Liste; sie bedient auch das Ziehen in dieser Liste.

Das Laden aus einer Projektdatei ist streng: ein Punkt ohne Zahlen fällt weg, ein kaputter Griff lässt einen Eckpunkt übrig, doppelte Form-Ids fallen weg wie doppelte Layer-Ids, eine Kontur der Breite 0 ist keine Kontur — während ein Ring mit nur einem Punkt, den gerade jemand zeichnet, stehenbleibt.

Drei Funde unterwegs:

  • JSON.stringify als Fingerabdruck war falsch: dieselbe Zeichnung kam aus der Projektdatei als {points, closed} zurück und wurde als {closed, points} gebaut — ein Reload hätte dasselbe Bild unter neuem Schlüssel neu gerastert. Der Fingerabdruck schreibt seine Felder jetzt in fester Reihenfolge, ein Test misst genau diesen Fall.
  • toNumber in serialize.ts macht aus jedem Unsinn eine 0, die Number.isFinite-Prüfungen liefen deshalb ins Leere: {x: 'far'} wurde zur linken Kante statt zu verschwinden. Formen lesen Zahlen streng über asFinite, weil 0 hier ein Ort auf der Leinwand ist.
  • „Vector art" wäre zugleich Menüeintrag und Panel-Überschrift gewesen — ein bestehender Test fängt das ab, weil ein Klick nach Text die Überschrift träfe. Die Gruppe heißt jetzt „Vector drawings", der Menüeintrag bleibt „Vector art".

20 neue Unit-Tests in tests/core/vectorart.test.ts; drei Gegenproben greifen (Fingerabdruck zurück auf JSON.stringify → 2 fallen, Zahlen wieder lose lesen → 4, Ringliste nicht kopieren → 1). Kein e2e-Check, Check-Summe blieb 1157, nur Modul 04 zählt 19 Asset-Typen statt 18.

Etappe 2 — die Rasterung (6371576)

Vector Art ist jetzt ein Bild. Das Asset steht im Bild-Picker, ein Sprite trägt es in seiner Texture-Zeile, und was der Sprite zeigt, hat der Rasterizer gemalt. Registriert wird per Funktion statt per Modul-Seiteneffekt: installVectorArtPictures(app.renderer) hängt Source (core) und Generator (viewport) einmal pro Seite in App.ts ein — ein Test darf sie also weglassen.

Die Regel liegt in core, gemalt wird in viewport. core/model/vectorPath.ts liefert pro Form reine Zeichenbefehle (move/line/curve/close): geschlossene Ringe füllen, alle Ringe konturieren, ein Segment bleibt eine Linie, solange keines seiner Enden einen Griff trägt — ein einzelner Griff genügt für eine Kurve, der fehlende Kontrollpunkt sitzt dann auf seinem eigenen Ende, damit eine Ecke neben einer runden Seite stehen kann. Der Rückweg zum ersten Punkt wird nur gezeichnet, wenn er gebogen ist; sonst macht close ihn.

Löcher gehen nur über einen Weg: Pixi erkennt sie ausschließlich an einem GraphicsPath mit checkForHoles — und prüft dabei Enthaltensein, nicht Even-Odd-Tiefe. Für Ring-in-Ring reicht das; ohne das Flag malt der innere Ring einfach über den äußeren (gemessen, siehe Gegenproben).

2× Überabtastung wie im Plan: RenderTexture.create({resolution: 2}) legt 512 px für eine 256er Leinwand auf die GPU, texture.width bleibt die Leinwandgröße — das ist das Maß, an dem der Sprite hängt. Geclippt wird durch die Rendertextur selbst: was über die Leinwand ragt, ist nicht drin, es braucht keine Maske. Gezeichnete Bilder bekommen denselben Sampler-Zustand wie hochgeladene (setPictureFactory, spiegelbildlich zu keep()).

Ein Punkt zum Mitlesen: renderer.render() läuft innerhalb der Bildanfrage. Das ist hier sicher, weil jeder Aufrufer während seines eigenen Widget-Redraws fragt und der Frame danach auf dem Ticker gezeichnet wird; ein verschachteltes render mitten in einem laufenden Renderpass wäre es nicht. Die Warnung steht im Modulkopf.

Tests

./check.sh                       → 126 Files, 1860 Tests, CHECK EXIT 0   (vorher 124 / 1817)
npm run build                    → BUILD EXIT 0
node tools/e2e.mjs (alle Module) → ALL PASSED – 1165 checks
GM_STEPS=47 (frisch geseedet)    → ALL PASSED – 10 checks

Neues Modul 47 mit 8 eigenen Checks in eigener Lane: das Bild liegt in doppelter Auflösung auf der GPU und misst die Leinwand; der Sprite zeigt die Füllung; der innere Ring ist ein Loch (die Szene scheint durch); die später gezeichnete Form liegt oben; die Kontur läuft am Ring; eine geänderte Zeichnung bekommt einen neuen Schlüssel und die alte wird freigegeben; der Sprite zeigt die neue Farbe. Die Formen legt der Test per mutate ins Modell — der Tab, der sie zeichnet, ist Etappe 3.

21 neue Unit-Tests in tests/core/vector-path.test.ts. Drei Gegenproben greifen: Überabtastung auf 1 → der GPU-Check fällt (pixelWidth: 256); checkForHoles aus → der Loch-Check fällt (#2e8b57 statt Hintergrund); den gebogenen Rückweg nicht zeichnen → ein Unit-Test fällt. Die volle Suite lief lokal statt nur in CI, weil die Änderung in der TextureLibrary jedes Bild berührt. Check-Summe 1165, gemessen.

Zwei Mess-Fallen, die Läufe gekostet haben: __gm.objectRect(id) gibt die Mitte des Objekts, nicht die Ecke — der erste Durchgang las vier Pixel neben dem Sprite und meldete Hintergrund, während der Rasterizer korrekt war; entschieden hat das der Screenshot, nicht die Zahlen. Und die Gitterlinien der Szene kreuzen genau im Mittelpunkt, der Loch-Check las #363940 und fiel aus dem richtigen Grund falsch; er misst jetzt daneben.

Offen

Etappe 3 (Tab: Stift, Auswahl, Griffe, polygonEdit.ts erweitern), Etappe 4 (Inspector: Formenliste mit Vorschau und Reihenfolge, Farbe/Deckkraft/Kontur), Etappe 5 (e2e zum Zeichnen, Wiki-Seite Vector Art).

**Etappen 1 und 2 sind gebaut und gepusht.** Die Grundlage #185 ist vorher geschlossen. Das Issue bleibt offen — es fehlen Etappe 3 (Tab), 4 (Inspector) und 5 (Tests/Doku zum Zeichnen). ## Etappe 1 — das Modell (`bdb3f06`) Der Asset-Typ `vectorart` existiert, nicht platzierbar, im `+`-Menü und im Asset-Panel mit eigenem Symbol (eine Kurve zwischen zwei Punkten). Einen Tab hat er noch nicht — er verhält sich wie eine Font: auswählbar, Inspector zeigt seine Zeilen, Doppelklick führt nirgendwohin. Ein halbes Asset wäre nur im Weg, der Tab ist Etappe 3. **Das Datenmodell** in `Asset.ts`: eine Form = mehrere **Ringe** (Even-Odd, also Löcher — Antwort 6), je Ring Punkte mit optionalen Bezier-Griffen `in`/`out` als Offset vom Punkt und `closed` ja/nein (offen = nur Kontur — Antwort 4), dazu Füllfarbe, Deckkraft und optional Kontur (Antwort 5). Die beiden Griffe liegen getrennt statt gespiegelt: was „glatt" heißt, ist Sache des Editors, nicht des Modells. Leinwandbreite, -höhe und Filter sind **Properties** (Inspector-Zeile und Undo wie jede andere), die Formen sind intrinsische Daten wie die Frames einer Textur. **Die Revision, die an #185 geht, ist ein Fingerabdruck des Gezeichneten, kein Zähler** — genau deshalb nahm der Schlüssel dort einen String-Token: kein Command kann vergessen, sie hochzuzählen. Der Name einer Form steckt bewusst nicht drin, Umbenennen ändert kein Pixel. **Sechs Undo-Commands** in `vectorArtCommands.ts` (Form hinzufügen, entfernen — sie kommt an ihre alte Stelle zurück, und anders als eine Tilemap-Ebene darf auch die letzte gehen —, umbenennen, verschieben, Ringe schreiben als ein Schritt pro Geste mit kopierter Liste, Stil setzen, wobei `stroke: null` die Kontur wegnimmt) plus die reine Funktion `shapeOrderTarget` für die vier Knöpfe der geplanten Inspector-Liste; sie bedient auch das Ziehen in dieser Liste. **Das Laden aus einer Projektdatei ist streng:** ein Punkt ohne Zahlen fällt weg, ein kaputter Griff lässt einen Eckpunkt übrig, doppelte Form-Ids fallen weg wie doppelte Layer-Ids, eine Kontur der Breite 0 ist keine Kontur — während ein Ring mit nur einem Punkt, den gerade jemand zeichnet, stehenbleibt. **Drei Funde unterwegs:** - `JSON.stringify` als Fingerabdruck war falsch: dieselbe Zeichnung kam aus der Projektdatei als `{points, closed}` zurück und wurde als `{closed, points}` gebaut — ein Reload hätte dasselbe Bild unter neuem Schlüssel neu gerastert. Der Fingerabdruck schreibt seine Felder jetzt in fester Reihenfolge, ein Test misst genau diesen Fall. - `toNumber` in `serialize.ts` macht aus jedem Unsinn eine 0, die `Number.isFinite`-Prüfungen liefen deshalb ins Leere: `{x: 'far'}` wurde zur linken Kante statt zu verschwinden. Formen lesen Zahlen streng über `asFinite`, weil 0 hier ein Ort auf der Leinwand ist. - „Vector art" wäre zugleich Menüeintrag und Panel-Überschrift gewesen — ein bestehender Test fängt das ab, weil ein Klick nach Text die Überschrift träfe. Die Gruppe heißt jetzt **„Vector drawings"**, der Menüeintrag bleibt „Vector art". 20 neue Unit-Tests in `tests/core/vectorart.test.ts`; drei Gegenproben greifen (Fingerabdruck zurück auf `JSON.stringify` → 2 fallen, Zahlen wieder lose lesen → 4, Ringliste nicht kopieren → 1). Kein e2e-Check, Check-Summe blieb 1157, nur Modul 04 zählt 19 Asset-Typen statt 18. ## Etappe 2 — die Rasterung (`6371576`) **Vector Art ist jetzt ein Bild.** Das Asset steht im Bild-Picker, ein Sprite trägt es in seiner *Texture*-Zeile, und was der Sprite zeigt, hat der Rasterizer gemalt. Registriert wird per Funktion statt per Modul-Seiteneffekt: `installVectorArtPictures(app.renderer)` hängt Source (core) und Generator (viewport) einmal pro Seite in `App.ts` ein — ein Test darf sie also weglassen. **Die Regel liegt in `core`, gemalt wird in `viewport`.** `core/model/vectorPath.ts` liefert pro Form reine Zeichenbefehle (`move/line/curve/close`): geschlossene Ringe füllen, alle Ringe konturieren, ein Segment bleibt eine Linie, solange keines seiner Enden einen Griff trägt — ein einzelner Griff genügt für eine Kurve, der fehlende Kontrollpunkt sitzt dann auf seinem eigenen Ende, damit eine Ecke neben einer runden Seite stehen kann. Der Rückweg zum ersten Punkt wird nur gezeichnet, wenn er gebogen ist; sonst macht `close` ihn. **Löcher gehen nur über einen Weg:** Pixi erkennt sie ausschließlich an einem `GraphicsPath` mit `checkForHoles` — und prüft dabei Enthaltensein, nicht Even-Odd-Tiefe. Für Ring-in-Ring reicht das; ohne das Flag malt der innere Ring einfach über den äußeren (gemessen, siehe Gegenproben). **2× Überabtastung wie im Plan:** `RenderTexture.create({resolution: 2})` legt 512 px für eine 256er Leinwand auf die GPU, `texture.width` bleibt die Leinwandgröße — das ist das Maß, an dem der Sprite hängt. Geclippt wird durch die Rendertextur selbst: was über die Leinwand ragt, ist nicht drin, es braucht keine Maske. Gezeichnete Bilder bekommen denselben Sampler-Zustand wie hochgeladene (`setPictureFactory`, spiegelbildlich zu `keep()`). **Ein Punkt zum Mitlesen:** `renderer.render()` läuft innerhalb der Bildanfrage. Das ist hier sicher, weil jeder Aufrufer während seines eigenen Widget-Redraws fragt und der Frame danach auf dem Ticker gezeichnet wird; ein verschachteltes `render` mitten in einem laufenden Renderpass wäre es nicht. Die Warnung steht im Modulkopf. ### Tests ``` ./check.sh → 126 Files, 1860 Tests, CHECK EXIT 0 (vorher 124 / 1817) npm run build → BUILD EXIT 0 node tools/e2e.mjs (alle Module) → ALL PASSED – 1165 checks GM_STEPS=47 (frisch geseedet) → ALL PASSED – 10 checks ``` Neues **Modul 47** mit 8 eigenen Checks in eigener Lane: das Bild liegt in doppelter Auflösung auf der GPU und misst die Leinwand; der Sprite zeigt die Füllung; der innere Ring ist ein Loch (die Szene scheint durch); die später gezeichnete Form liegt oben; die Kontur läuft am Ring; eine geänderte Zeichnung bekommt einen neuen Schlüssel und die alte wird freigegeben; der Sprite zeigt die neue Farbe. Die Formen legt der Test per `mutate` ins Modell — der Tab, der sie zeichnet, ist Etappe 3. 21 neue Unit-Tests in `tests/core/vector-path.test.ts`. Drei Gegenproben greifen: Überabtastung auf 1 → der GPU-Check fällt (`pixelWidth: 256`); `checkForHoles` aus → der Loch-Check fällt (`#2e8b57` statt Hintergrund); den gebogenen Rückweg nicht zeichnen → ein Unit-Test fällt. Die volle Suite lief lokal statt nur in CI, weil die Änderung in der `TextureLibrary` jedes Bild berührt. **Check-Summe 1165, gemessen.** **Zwei Mess-Fallen, die Läufe gekostet haben:** `__gm.objectRect(id)` gibt die **Mitte** des Objekts, nicht die Ecke — der erste Durchgang las vier Pixel neben dem Sprite und meldete Hintergrund, während der Rasterizer korrekt war; entschieden hat das der Screenshot, nicht die Zahlen. Und die Gitterlinien der Szene kreuzen genau im Mittelpunkt, der Loch-Check las `#363940` und fiel aus dem richtigen Grund falsch; er misst jetzt daneben. ## Offen Etappe 3 (Tab: Stift, Auswahl, Griffe, `polygonEdit.ts` erweitern), Etappe 4 (Inspector: Formenliste mit Vorschau und Reihenfolge, Farbe/Deckkraft/Kontur), Etappe 5 (e2e zum Zeichnen, Wiki-Seite *Vector Art*).
Owner

Etappe 3 ist gebaut (Commit 3d7ad65), dazu ein Fix, den der Test im Browser nicht gefunden hat, sondern der Benutzer: das Ziehen eines Punktes hinkte der Maus hinterher (Commit 3a68067).

Etappe 3 — der Tab

Kopfzeile mit Select / Pen / Grid / Fit, Schachbrett, die Leinwand als Rahmen, darin die Zeichnung — gemalt mit denselben Befehlen wie der Rasterizer (paintDrawing ist dafür aus viewport/vectorArtPicture.ts exportiert), so dass Tab und Bild nie auseinanderlaufen können.

  • Pen: Klick setzt Punkte, Ziehen zieht die Griffe heraus (beide, gespiegelt), Klick auf den ersten Punkt schließt den Ring, Esc beendet die Linie.
  • Select: Punkt, Griff und ganze Form ziehen, Klick auf eine Kante setzt dort einen Punkt, Doppelklick nimmt einen weg, Alt macht eine Ecke rund und wieder eckig, Delete entfernt die ausgewählte Form.
  • Grid rastet auf 8 px ein, Vorgabe aus.

Die Regeln sind in polygonEdit.ts gewachsen, kein zweites Modul — wie im Plan (Antwort 7). Zwei Punkte daraus sind mehr als Fleißarbeit: ein Punkt auf einer gebogenen Kante spaltet die Kurve (de Casteljau) statt sie abzuschneiden, beide Hälften zeichnen zusammen genau das, was vorher da war; und eine gebogene Kante wird abgelaufen, nicht über ihre Sehne gemessen, sonst greift ein Klick auf die Kurve ins Leere. Eine Geste ist ein Undo-Schritt (Vorschau schreibt direkt, beim Loslassen wird der alte Stand zurückgelegt und das Kommando ausgeführt) — dasselbe Muster wie bei den Frame-Umrissen aus #141. Neu nötig war eine Auswahlart shape in state.ts.

Tests: check.sh 127 Dateien / 1878 Tests, ALL PASSED – 1172 checks, 18 neue Unit-Tests in tests/editor/ring-edit.test.ts, 7 neue e2e-Checks in 47c/47d.

Ein Fund, der festgehalten gehört: eine Gegenprobe, die keine war. Das Schließen des Stifts mit * 0 entschärft ließ den Lauf grün — zu Recht, denn der schließende Klick landet auf exakt der Bildschirmkoordinate von Punkt 0, die Distanz ist 0 und <= 0 ist wahr. Erst eine unerfüllbare Bedingung schaltete wirklich ab (2 Checks fallen). Dazu: ein false && vor einem Guard bricht das TS-Narrowing, der Build steigt aus, und zwei Läufe gingen gegen das alte Bundle.

Der Fix: Ziehen hält jetzt mit der Maus Schritt

Gemeldet als „aktualisiert erst, wenn ich die Maus stehen lasse". Das ist ein echter Rückstau, kein Gefühl — gemessen mit einem Wegwerf-Modul, das 120 Maus-Events im 8-ms-Takt schickt (125 Hz):

Objekte in der Szene ein preview() je Move-Event vorher nachher
0 3,7 ms 10 ms 7 ms
40 5,4 ms 19 ms 4 ms
120 10,3 ms 36 ms 3 ms

Die 120 Moves dauerten vorher 4173 ms statt der gesollten 960 — der Rest war Rückstau in der Eingabeschlange, genau das Symptom.

Ursache: Jede Mausbewegung schrieb das Modell und feuerte state.preview() → emit('project'), und daran hängt die ganze Editor-Kette (Play-Sync, Invalidieren des Editor-Laufs, alle Dokumente und Panels, dazu das Neurastern des gezeichneten Bildes). Das ist Arbeit für einen Frame, nicht für jedes Event.

Fix: onFrame(key, fn) in src/ui/render.ts sammelt solche Arbeit pro Schlüssel und führt sie im Ticker vor app.render() aus — danach wäre sie einen Frame zu spät im Bild. Der Canvas stellt im pointermove nur noch ein und räumt seinen Platz beim Loslassen (cancelFrameWork), weil dann das Kommando den Endstand schreibt. Während der Geste ist die eigene Ring-Kopie die Wahrheit, das Modell wird nie zurückgelesen, es geht also nichts verloren.

Tests: check.sh 127 Dateien / 1883 Tests, volle Suite lokal (weil die Änderung im Render-Loop sitzt): ALL PASSED – 1172 checks, Summe unverändert — ein Zeitcheck im Browser wäre unter CI-Last wackelig, der Beleg ist die Node-Messung. 5 neue Unit-Tests in tests/ui/render.test.ts. Zwei Gegenproben greifen: die Arbeit nach dem Zeichnen laufen lassen lässt einen Unit-Test fallen, das Coalescing im Canvas ausbauen bringt die Messung zurück auf 36 ms / 4153 ms. Vom Benutzer im eigenen Projekt nachgeprüft.

Nicht angefasst: Szenen-Viewport, Texture-Tab und Graph rufen preview() genauso im Move-Handler und haben dieselbe Kostenstelle. Das liegt außerhalb des gemeldeten Fehlers; onFrame liegt allgemein in ui/ und wäre ein Kandidat für die nächste Refactoring-Runde (#85).

Offen

Etappe 4 (Inspector: Formenliste mit Vorschau, Reihenfolge-Knöpfe, Farbe/Deckkraft/Kontur) und Etappe 5 (Wiki-Seite Vector Art, restliche Doku). Das Issue bleibt offen.

**Etappe 3 ist gebaut** (Commit `3d7ad65`), dazu ein Fix, den der Test im Browser nicht gefunden hat, sondern der Benutzer: **das Ziehen eines Punktes hinkte der Maus hinterher** (Commit `3a68067`). ### Etappe 3 — der Tab Kopfzeile mit **Select / Pen / Grid / Fit**, Schachbrett, die Leinwand als Rahmen, darin die Zeichnung — gemalt mit denselben Befehlen wie der Rasterizer (`paintDrawing` ist dafür aus `viewport/vectorArtPicture.ts` exportiert), so dass Tab und Bild nie auseinanderlaufen können. - **Pen:** Klick setzt Punkte, Ziehen zieht die Griffe heraus (beide, gespiegelt), Klick auf den ersten Punkt schließt den Ring, Esc beendet die Linie. - **Select:** Punkt, Griff und ganze Form ziehen, Klick auf eine Kante setzt dort einen Punkt, Doppelklick nimmt einen weg, Alt macht eine Ecke rund und wieder eckig, Delete entfernt die ausgewählte Form. - **Grid** rastet auf 8 px ein, Vorgabe aus. Die Regeln sind in `polygonEdit.ts` gewachsen, kein zweites Modul — wie im Plan (Antwort 7). Zwei Punkte daraus sind mehr als Fleißarbeit: ein Punkt auf einer gebogenen Kante **spaltet die Kurve** (de Casteljau) statt sie abzuschneiden, beide Hälften zeichnen zusammen genau das, was vorher da war; und eine gebogene Kante wird **abgelaufen**, nicht über ihre Sehne gemessen, sonst greift ein Klick auf die Kurve ins Leere. Eine Geste ist ein Undo-Schritt (Vorschau schreibt direkt, beim Loslassen wird der alte Stand zurückgelegt und das Kommando ausgeführt) — dasselbe Muster wie bei den Frame-Umrissen aus #141. Neu nötig war eine Auswahlart `shape` in `state.ts`. **Tests:** `check.sh` 127 Dateien / 1878 Tests, `ALL PASSED – 1172 checks`, 18 neue Unit-Tests in `tests/editor/ring-edit.test.ts`, 7 neue e2e-Checks in 47c/47d. **Ein Fund, der festgehalten gehört: eine Gegenprobe, die keine war.** Das Schließen des Stifts mit `* 0` entschärft ließ den Lauf grün — zu Recht, denn der schließende Klick landet auf exakt der Bildschirmkoordinate von Punkt 0, die Distanz ist 0 und `<= 0` ist wahr. Erst eine unerfüllbare Bedingung schaltete wirklich ab (2 Checks fallen). Dazu: ein `false &&` vor einem Guard bricht das TS-Narrowing, der Build steigt aus, und zwei Läufe gingen gegen das alte Bundle. ### Der Fix: Ziehen hält jetzt mit der Maus Schritt Gemeldet als „aktualisiert erst, wenn ich die Maus stehen lasse". Das ist ein echter Rückstau, kein Gefühl — gemessen mit einem Wegwerf-Modul, das 120 Maus-Events im 8-ms-Takt schickt (125 Hz): | Objekte in der Szene | ein `preview()` | je Move-Event **vorher** | **nachher** | |---|---|---|---| | 0 | 3,7 ms | 10 ms | 7 ms | | 40 | 5,4 ms | 19 ms | 4 ms | | 120 | 10,3 ms | 36 ms | 3 ms | Die 120 Moves dauerten vorher **4173 ms** statt der gesollten 960 — der Rest war Rückstau in der Eingabeschlange, genau das Symptom. **Ursache:** Jede Mausbewegung schrieb das Modell und feuerte `state.preview()` → `emit('project')`, und daran hängt die ganze Editor-Kette (Play-Sync, Invalidieren des Editor-Laufs, alle Dokumente und Panels, dazu das Neurastern des gezeichneten Bildes). Das ist Arbeit für einen Frame, nicht für jedes Event. **Fix:** `onFrame(key, fn)` in `src/ui/render.ts` sammelt solche Arbeit pro Schlüssel und führt sie im Ticker **vor** `app.render()` aus — danach wäre sie einen Frame zu spät im Bild. Der Canvas stellt im `pointermove` nur noch ein und räumt seinen Platz beim Loslassen (`cancelFrameWork`), weil dann das Kommando den Endstand schreibt. Während der Geste ist die eigene Ring-Kopie die Wahrheit, das Modell wird nie zurückgelesen, es geht also nichts verloren. **Tests:** `check.sh` 127 Dateien / 1883 Tests, volle Suite lokal (weil die Änderung im Render-Loop sitzt): `ALL PASSED – 1172 checks`, Summe unverändert — ein Zeitcheck im Browser wäre unter CI-Last wackelig, der Beleg ist die Node-Messung. 5 neue Unit-Tests in `tests/ui/render.test.ts`. Zwei Gegenproben greifen: die Arbeit nach dem Zeichnen laufen lassen lässt einen Unit-Test fallen, das Coalescing im Canvas ausbauen bringt die Messung zurück auf 36 ms / 4153 ms. Vom Benutzer im eigenen Projekt nachgeprüft. **Nicht angefasst:** Szenen-Viewport, Texture-Tab und Graph rufen `preview()` genauso im Move-Handler und haben dieselbe Kostenstelle. Das liegt außerhalb des gemeldeten Fehlers; `onFrame` liegt allgemein in `ui/` und wäre ein Kandidat für die nächste Refactoring-Runde (#85). ### Offen Etappe 4 (Inspector: Formenliste mit Vorschau, Reihenfolge-Knöpfe, Farbe/Deckkraft/Kontur) und Etappe 5 (Wiki-Seite *Vector Art*, restliche Doku). Das Issue bleibt offen.
Owner

Etappe 4 ist gebaut, committet und gepusht (78595ec) — das Issue bleibt offen, Etappe 5 (Wiki-Seite Vector Art und restliche Doku) folgt direkt.

Was jetzt da ist

Eine ausgewählte Form hatte bisher die Zeilen ihres Assets — Farbe, Kontur und Reihenfolge einer Form waren also nur über die Projektdatei erreichbar. Jetzt zeigt der Inspector SHAPES (die Liste), die vier Reihenfolge-Knöpfe, VECTOR SHAPE (Name, Fill, Opacity, Outline; mit Kontur zusätzlich Line colour und Line width), Delete shape und Add shape. Wählt man das Asset, steht dieselbe Liste unter seinen Zeilen, ohne dass eine Form gewählt ist. Die Leinwandgröße bleibt eine gewöhnliche Property am Asset.

  • Jede Zeile zeigt ihre Form, gemalt mit denselben Befehlen wie der Rasterizer (shapeDrawing + paintDrawing) in einen 22-px-Swatch, die ganze Leinwand hineinskaliert — die Zeile sagt also auch, wo auf der Leinwand die Form sitzt. Ein Punkt, der im Tab gezogen wird, zieht die Vorschau mit.
  • Die Reihenfolge ist die Liste: oberste Zeile = zuletzt gezeichnet, wie in jeder Layer-Liste. Ziehen einer Zeile mit Markierungslinie, dazu die vier Knöpfe aus dem Nachtrag (eins vor, ganz nach vorne, eins zurück, ganz nach hinten) — deaktiviert, wenn es nichts zu verschieben gibt.
  • Ein gezogener Farbregler ist ein Undo-Schritt: die Stilzeilen laufen über previewingHandlers, beim Loslassen ein SetVectorShapeStyleCommand. Die Kontur wird per Häkchen hinzugefügt oder weggenommen, nicht unsichtbar gemacht; Linienbreite 0 ist keine Kontur, das Kommando lehnt sie ab, die Zeile klemmt auf 0,1.

Tests

./check.sh                       -> 128 Files, 1897 Tests, CHECK EXIT 0   (vorher 127 / 1883)
npm run build                    -> BUILD EXIT 0
node tools/e2e.mjs (alle Module) -> ALL PASSED - 1179 checks   (vorher 1172)
GM_STEPS=47 (frisch geseedet)    -> ALL PASSED - 24 checks     (vorher 17)
  • 14 neue Unit-Tests in tests/editor/shape-rows.test.ts: die Arithmetik der Liste (Reihenfolge, Ziel einer abgelegten Zeile) und die echten Zeilen (welche Zeilen mit und ohne Kontur, Fill als ein Undo-Schritt, Opacity geklemmt, Häkchen gibt und nimmt die Kontur, Signatur, Refresh).
  • 7 neue e2e-Checks (Block 47e): die Liste zeigt die Modellreihenfolge umgedreht, ein Klick auf eine Zeile wählt die Form, die Fill-Zeile färbt um und der Tab zeigt es, das Häkchen bringt die Konturzeilen, „To front" legt die Form über die, die sie verdeckt hat (am Pixel gemessen), Ziehen einer Zeile ans Ende zeichnet sie zuerst, „Add shape" legt eine leere Form an und wählt sie.
  • Volle Suite lokal statt nur CI, weil die Änderung im Inspector sitzt und damit jede Auswahl berührt.
  • Drei Gegenproben, alle greifen: Reihenfolge nicht umdrehen -> 1 Unit-Test und 2 e2e-Checks fallen; die Lücken-Korrektur beim Ablegen weg -> 2 Unit-Tests und der Zieh-Check; Signatur auf eine Konstante -> Modul 47 stirbt, weil die Konturzeilen nach dem Einschalten nie erscheinen. Originale per cmp zurückgeprüft und neu gebaut.
  • Screenshot angesehen (47-shape-rows): Zeilen mit Vorschau, ausgewählte Zeile markiert, Knöpfe, Fill #20c0e8, Häkchen, Line colour/width.

Drei Funde aus dieser Etappe

  • locate('Outline') zielt daneben. Der Helfer gibt die Mitte des Label-Textes, und „Outline" ist breiter als „Fill" — ein fester Versatz landet mal im Feld, mal im Nichts. Richtig ist __gm.inspectorField(label), der die Mitte des Bedienelements kennt.
  • Ein Klick in ein Textfeld setzt den Cursor, er wählt nichts aus. Das getippte 20c0e8 landete als #e8c02020c0e8 im Hex-Feld, parseHex gab null, nichts passierte — und typeText wirft dabei nicht, weil es nur prüft, ob der Text enthalten ist. Jetzt geht ein Ctrl+A voraus.
  • Vier Knöpfe nebeneinander kürzen „Backward" auf „Backw…". Gesehen hat das nur der Screenshot, kein Check; jetzt zwei Reihen zu zweit, dasselbe Muster wie im Tilemap-Panel.

Doku

Wiki c473762: Architecture (shapeSection, ShapeList, shapeRows samt der Regel, warum die Liste auf dem Kopf steht), Development (Check-Summe). README-Check-Summe 1179 — gemessen, nicht gerechnet. Die eigene Seite Vector Art gehört laut Plan in Etappe 5.

Offen bleibt Etappe 5: die Wiki-Seite Vector Art und die restliche Doku.

**Etappe 4 ist gebaut, committet und gepusht** (`78595ec`) — das Issue bleibt offen, Etappe 5 (Wiki-Seite *Vector Art* und restliche Doku) folgt direkt. ## Was jetzt da ist Eine ausgewählte Form hatte bisher die Zeilen ihres **Assets** — Farbe, Kontur und Reihenfolge einer Form waren also nur über die Projektdatei erreichbar. Jetzt zeigt der Inspector **SHAPES** (die Liste), die vier Reihenfolge-Knöpfe, **VECTOR SHAPE** (Name, Fill, Opacity, Outline; mit Kontur zusätzlich Line colour und Line width), *Delete shape* und *Add shape*. Wählt man das Asset, steht dieselbe Liste unter seinen Zeilen, ohne dass eine Form gewählt ist. Die Leinwandgröße bleibt eine gewöhnliche Property am Asset. - **Jede Zeile zeigt ihre Form**, gemalt mit denselben Befehlen wie der Rasterizer (`shapeDrawing` + `paintDrawing`) in einen 22-px-Swatch, die ganze Leinwand hineinskaliert — die Zeile sagt also auch, *wo* auf der Leinwand die Form sitzt. Ein Punkt, der im Tab gezogen wird, zieht die Vorschau mit. - **Die Reihenfolge ist die Liste**: oberste Zeile = zuletzt gezeichnet, wie in jeder Layer-Liste. Ziehen einer Zeile mit Markierungslinie, dazu die vier Knöpfe aus dem Nachtrag (eins vor, ganz nach vorne, eins zurück, ganz nach hinten) — deaktiviert, wenn es nichts zu verschieben gibt. - **Ein gezogener Farbregler ist ein Undo-Schritt**: die Stilzeilen laufen über `previewingHandlers`, beim Loslassen ein `SetVectorShapeStyleCommand`. Die Kontur wird per Häkchen hinzugefügt oder weggenommen, nicht unsichtbar gemacht; Linienbreite 0 ist keine Kontur, das Kommando lehnt sie ab, die Zeile klemmt auf 0,1. ## Tests ``` ./check.sh -> 128 Files, 1897 Tests, CHECK EXIT 0 (vorher 127 / 1883) npm run build -> BUILD EXIT 0 node tools/e2e.mjs (alle Module) -> ALL PASSED - 1179 checks (vorher 1172) GM_STEPS=47 (frisch geseedet) -> ALL PASSED - 24 checks (vorher 17) ``` - **14 neue Unit-Tests** in `tests/editor/shape-rows.test.ts`: die Arithmetik der Liste (Reihenfolge, Ziel einer abgelegten Zeile) und die echten Zeilen (welche Zeilen mit und ohne Kontur, Fill als ein Undo-Schritt, Opacity geklemmt, Häkchen gibt und nimmt die Kontur, Signatur, Refresh). - **7 neue e2e-Checks** (Block 47e): die Liste zeigt die Modellreihenfolge umgedreht, ein Klick auf eine Zeile wählt die Form, die Fill-Zeile färbt um *und der Tab zeigt es*, das Häkchen bringt die Konturzeilen, „To front" legt die Form über die, die sie verdeckt hat (am Pixel gemessen), Ziehen einer Zeile ans Ende zeichnet sie zuerst, „Add shape" legt eine leere Form an und wählt sie. - **Volle Suite lokal statt nur CI**, weil die Änderung im Inspector sitzt und damit jede Auswahl berührt. - **Drei Gegenproben, alle greifen**: Reihenfolge nicht umdrehen -> 1 Unit-Test **und** 2 e2e-Checks fallen; die Lücken-Korrektur beim Ablegen weg -> 2 Unit-Tests und der Zieh-Check; Signatur auf eine Konstante -> Modul 47 stirbt, weil die Konturzeilen nach dem Einschalten nie erscheinen. Originale per `cmp` zurückgeprüft und neu gebaut. - **Screenshot angesehen** (`47-shape-rows`): Zeilen mit Vorschau, ausgewählte Zeile markiert, Knöpfe, Fill `#20c0e8`, Häkchen, Line colour/width. ## Drei Funde aus dieser Etappe - **`locate('Outline')` zielt daneben.** Der Helfer gibt die Mitte des Label-*Textes*, und „Outline" ist breiter als „Fill" — ein fester Versatz landet mal im Feld, mal im Nichts. Richtig ist `__gm.inspectorField(label)`, der die Mitte des Bedienelements kennt. - **Ein Klick in ein Textfeld setzt den Cursor, er wählt nichts aus.** Das getippte `20c0e8` landete als `#e8c02020c0e8` im Hex-Feld, `parseHex` gab null, nichts passierte — und `typeText` **wirft dabei nicht**, weil es nur prüft, ob der Text enthalten ist. Jetzt geht ein Ctrl+A voraus. - **Vier Knöpfe nebeneinander kürzen „Backward" auf „Backw…".** Gesehen hat das nur der Screenshot, kein Check; jetzt zwei Reihen zu zweit, dasselbe Muster wie im Tilemap-Panel. ## Doku Wiki `c473762`: Architecture (`shapeSection`, `ShapeList`, `shapeRows` samt der Regel, warum die Liste auf dem Kopf steht), Development (Check-Summe). README-Check-Summe **1179** — gemessen, nicht gerechnet. Die eigene Seite *Vector Art* gehört laut Plan in Etappe 5. **Offen bleibt Etappe 5**: die Wiki-Seite *Vector Art* und die restliche Doku.
Owner

Etappe 5 ist fertig — #129 ist damit durch

ad62a0f (Repo) und 21bea26 (Wiki).

Der Nutzerweg ist jetzt gemessen

Die Etappen 1 bis 4 haben die Formen per mutate ins Modell gelegt, weil der Tab, der sie zeichnet, erst Etappe 3 war. Block 47f geht jetzt den Weg, den du gehst:

  • Das +-Menü bietet Vector art unter den Typen, die nur in der Bibliothek leben; der Eintrag legt ein Asset an, ausgewählt und im Panel stehend.
  • Ein Doppelklick auf die Zeile öffnet den Vector-Art-Tab auf diesem Asset — das hatte Etappe 3 gebaut, aber kein Check hat es bisher gemessen.
  • Die Texture-Zeile eines Sprites bietet die Zeichnung nach Namen an, einsortiert zwischen den übrigen Bildern, und die Auswahl dort lässt den Sprite sie tragen — am Pixel in der Szene gemessen, nicht an der Zeile.

Ein Fund: Der Picker zeigte drei Zeilen, wo ich mehr erwartet hatte. Der Grund ist in Ordnung — ein frisches Fixture hat eine Textur und die Zeichnung, „None" ist die dritte. Der Check nennt jetzt alle drei, statt sie zu zählen: geht eine verloren, sagt er welche.

Die Doku

Vector Art war in Stücken dokumentiert — Textures trug das erzeugte Bild, Architecture die Module, Keyboard den Tab. Es gibt jetzt eine eigene Wiki-Seite Vector Art, die vom ersten Klick bis zum fertigen Sprite durchliest: Asset und Leinwand, die zwei Werkzeuge, was ein Griff tut, wie Ringe ein Loch machen, die Formenliste samt Reihenfolge — und die Grenzen, die eigene Issues sind (#186, #187, #188, #189). Sidebar und Home tragen die Seite; Textures und Tiles verweisen darauf, statt es ein zweites Mal in eigenen Worten zu beschreiben.

Tests

./check.sh                       → 128 Files, 1897 Tests, CHECK EXIT 0
node tools/e2e.mjs (alle Module) → ALL PASSED – 1184 checks   (vorher 1179)
GM_STEPS=47 (frisch geseedet)    → ALL PASSED – 29 checks     (vorher 24)

Die Unit-Tests sind seit Etappe 4 unverändert — Etappe 5 ist Browser-Beleg und Doku, keine neue Regel. Beide Gegenproben greifen: keinen Erzeuger registrieren → der Picker-Check fällt; den Tab ohne das Asset öffnen → der Doppelklick-Check fällt. Screenshot angesehen.

Was #129 insgesamt gebracht hat

Etappe Commit was
Grundlage (#185) cb1804d erzeugte Bilder im Bildpfad
1 bdb3f06 das Modell: Ringe, Griffe, Löcher, sechs Undo-Commands
2 6371576 die Rasterung, 2× überabgetastet, an der Leinwand geclippt
3 3d7ad65 der Tab: Stift, Auswahl, Griffe
— 3a68067 das Ziehen hält mit der Maus mit (36 → 3 ms je Event)
4 78595ec die Inspector-Zeilen einer Form samt Formenliste
5 ad62a0f der Nutzerweg als Check, die Wiki-Seite

Die Check-Summe ist von 1157 auf 1184 gewachsen, die Unit-Tests von 1799 auf 1897.

Offen bleibt bewusst: Verläufe (#186), boolesche Operationen (#187), Collider aus einer Form (#188), SVG-Import (#189), Gruppen und Layers (#190), ein eigenes Panel statt Inspector (#191). Laufzeit-Nodes sind nach deiner Antwort 10 nicht angelegt.

Auf den CI-Lauf zu ad62a0f habe ich nicht gewartet — erst der bestätigt die Zahlen oben auf einer fremden Maschine.

## Etappe 5 ist fertig — #129 ist damit durch `ad62a0f` (Repo) und `21bea26` (Wiki). ### Der Nutzerweg ist jetzt gemessen Die Etappen 1 bis 4 haben die Formen per `mutate` ins Modell gelegt, weil der Tab, der sie zeichnet, erst Etappe 3 war. Block **47f** geht jetzt den Weg, den du gehst: - Das `+`-Menü bietet **Vector art** unter den Typen, die nur in der Bibliothek leben; der Eintrag legt ein Asset an, ausgewählt und im Panel stehend. - Ein Doppelklick auf die Zeile öffnet den **Vector-Art-Tab** auf diesem Asset — das hatte Etappe 3 gebaut, aber kein Check hat es bisher gemessen. - Die *Texture*-Zeile eines Sprites bietet die Zeichnung **nach Namen** an, einsortiert zwischen den übrigen Bildern, und die Auswahl dort lässt den Sprite sie tragen — **am Pixel in der Szene gemessen**, nicht an der Zeile. **Ein Fund:** Der Picker zeigte drei Zeilen, wo ich mehr erwartet hatte. Der Grund ist in Ordnung — ein frisches Fixture hat eine Textur und die Zeichnung, „None" ist die dritte. Der Check **nennt jetzt alle drei**, statt sie zu zählen: geht eine verloren, sagt er welche. ### Die Doku Vector Art war in Stücken dokumentiert — Textures trug das erzeugte Bild, Architecture die Module, Keyboard den Tab. Es gibt jetzt eine **eigene Wiki-Seite *Vector Art***, die vom ersten Klick bis zum fertigen Sprite durchliest: Asset und Leinwand, die zwei Werkzeuge, was ein Griff tut, wie Ringe ein Loch machen, die Formenliste samt Reihenfolge — und die Grenzen, die eigene Issues sind (#186, #187, #188, #189). Sidebar und Home tragen die Seite; Textures und Tiles verweisen darauf, statt es ein zweites Mal in eigenen Worten zu beschreiben. ### Tests ``` ./check.sh → 128 Files, 1897 Tests, CHECK EXIT 0 node tools/e2e.mjs (alle Module) → ALL PASSED – 1184 checks (vorher 1179) GM_STEPS=47 (frisch geseedet) → ALL PASSED – 29 checks (vorher 24) ``` Die Unit-Tests sind seit Etappe 4 unverändert — Etappe 5 ist Browser-Beleg und Doku, keine neue Regel. **Beide Gegenproben greifen:** keinen Erzeuger registrieren → der Picker-Check fällt; den Tab ohne das Asset öffnen → der Doppelklick-Check fällt. Screenshot angesehen. ### Was #129 insgesamt gebracht hat | Etappe | Commit | was | |---|---|---| | Grundlage (#185) | `cb1804d` | erzeugte Bilder im Bildpfad | | 1 | `bdb3f06` | das Modell: Ringe, Griffe, Löcher, sechs Undo-Commands | | 2 | `6371576` | die Rasterung, 2× überabgetastet, an der Leinwand geclippt | | 3 | `3d7ad65` | der Tab: Stift, Auswahl, Griffe | | — | `3a68067` | das Ziehen hält mit der Maus mit (36 → 3 ms je Event) | | 4 | `78595ec` | die Inspector-Zeilen einer Form samt Formenliste | | 5 | `ad62a0f` | der Nutzerweg als Check, die Wiki-Seite | Die Check-Summe ist von 1157 auf **1184** gewachsen, die Unit-Tests von 1799 auf **1897**. **Offen bleibt bewusst:** Verläufe (#186), boolesche Operationen (#187), Collider aus einer Form (#188), SVG-Import (#189), Gruppen und Layers (#190), ein eigenes Panel statt Inspector (#191). Laufzeit-Nodes sind nach deiner Antwort 10 nicht angelegt. Auf den CI-Lauf zu `ad62a0f` habe ich nicht gewartet — erst der bestätigt die Zahlen oben auf einer fremden Maschine.
admin moved this to Done in Development on 2026-09-21 06:05:06 +02:00
Sign in to join this conversation.
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: meleki/feuerwerk#129