Vorgezogen aus #129 (Frage 12, Antwort: „gemeinsame Grundlage vorziehen"). Diese Stelle brauchen #129 (Vector Art) und #130 (Pixel Editor) gleichermaßen — sie soll genau einmal gebaut werden.
Befund
Ein Bild ist heute immer eine Datei auf dem Server:
TextureRef.file ist ein Dateiname, isTextureFile() prüft Hash + Endung (src/core/model/texture.ts).
TextureLibrary.load() holt diesen Namen per HTTP.
resolvePicture() (src/core/model/texture.ts:86) ist der eine Weg, über den Sprite, Tile, Tilemap, Partikelquelle und der Bild-Picker an ein Bild kommen.
Ein erzeugtes Bild — gerastert aus Vector Art, gemalt im Pixel Editor — hat keine Datei. Ohne diese Grundlage müsste jeder Erzeuger seinen eigenen Sonderweg durch den Bildpfad bauen.
Zu bauen (Vorschlag)
file bleibt der Schlüssel, darf aber auch ein erzeugter sein, Form gen:<assetId>@<rev>. TextureLibrary.load() fragt für so einen Schlüssel ein Register von Erzeugern statt den Server. Zuschnitt, Filter-Modus, Shader, Tilemap-Mesh und Partikel sehen weiterhin eine Texture und bleiben unverändert.
Neu gezeichnet wird über einen Revisionszähler am Asset: neue Revision → neuer Schlüssel → die Library rastert neu und gibt den alten Eintrag frei. Kein „ist das Bild noch gültig"-Rätselraten an zwanzig Stellen.
resolvePicture() liefert für ein erzeugendes Asset einen Ref mit der Leinwandgröße als sourceWidth/Height, damit properties/aspect.ts ohne Sonderfall die Sprite-Größe bestimmt.
Bild-Picker und Nutzungssuche (usages.ts) nehmen den neuen Asset-Typ mit.
Der Vertrag („was ein Erzeuger liefern muss") kommt ins Wiki (Architecture, Textures).
Tests
Unit: Schlüssel bauen und zerlegen, Revision erhöht → neuer Schlüssel, alter Eintrag freigegeben, resolvePicture() auf ein erzeugendes Asset, Erzeuger-Register ohne GPU (Fake-Erzeuger).
e2e folgt mit dem ersten echten Erzeuger (#129) — allein ist die Grundlage im Browser nicht sichtbar.
Offen
Ob eine erzeugte Textur zusätzlich zwischengespeichert wird (IndexedDB o. ä.), damit sie nach einem Neuladen nicht neu gerastert werden muss. Vorschlag: nein, erst wenn es messbar stört.
Vorgezogen aus #129 (Frage 12, Antwort: „gemeinsame Grundlage vorziehen"). Diese Stelle brauchen #129 (Vector Art) und #130 (Pixel Editor) gleichermaßen — sie soll genau einmal gebaut werden.
## Befund
Ein Bild ist heute immer eine **Datei auf dem Server**:
- `TextureRef.file` ist ein Dateiname, `isTextureFile()` prüft Hash + Endung (`src/core/model/texture.ts`).
- `TextureLibrary.load()` holt diesen Namen per HTTP.
- `resolvePicture()` (`src/core/model/texture.ts:86`) ist der eine Weg, über den Sprite, Tile, Tilemap, Partikelquelle und der Bild-Picker an ein Bild kommen.
Ein **erzeugtes** Bild — gerastert aus Vector Art, gemalt im Pixel Editor — hat keine Datei. Ohne diese Grundlage müsste jeder Erzeuger seinen eigenen Sonderweg durch den Bildpfad bauen.
## Zu bauen (Vorschlag)
- **`file` bleibt der Schlüssel**, darf aber auch ein erzeugter sein, Form `gen:<assetId>@<rev>`. `TextureLibrary.load()` fragt für so einen Schlüssel ein **Register von Erzeugern** statt den Server. Zuschnitt, Filter-Modus, Shader, Tilemap-Mesh und Partikel sehen weiterhin eine `Texture` und bleiben **unverändert**.
- **Neu gezeichnet wird über einen Revisionszähler** am Asset: neue Revision → neuer Schlüssel → die Library rastert neu und gibt den alten Eintrag frei. Kein „ist das Bild noch gültig"-Rätselraten an zwanzig Stellen.
- `resolvePicture()` liefert für ein erzeugendes Asset einen Ref mit der **Leinwandgröße** als `sourceWidth/Height`, damit `properties/aspect.ts` ohne Sonderfall die Sprite-Größe bestimmt.
- **Bild-Picker** und **Nutzungssuche** (`usages.ts`) nehmen den neuen Asset-Typ mit.
- Der Vertrag („was ein Erzeuger liefern muss") kommt ins Wiki (Architecture, Textures).
## Tests
- Unit: Schlüssel bauen und zerlegen, Revision erhöht → neuer Schlüssel, alter Eintrag freigegeben, `resolvePicture()` auf ein erzeugendes Asset, Erzeuger-Register ohne GPU (Fake-Erzeuger).
- e2e folgt mit dem ersten echten Erzeuger (#129) — allein ist die Grundlage im Browser nicht sichtbar.
## Offen
- Ob eine erzeugte Textur zusätzlich **zwischengespeichert** wird (IndexedDB o. ä.), damit sie nach einem Neuladen nicht neu gerastert werden muss. Vorschlag: nein, erst wenn es messbar stört.
meleki
added the new label 2026-09-20 19:46:13 +02:00
Gebaut und gepusht: cb1804d (10 Dateien, 6 geändert, 4 neu), Wiki dfecee8. Der Erzeuger, der die Grundlage sichtbar macht, kam mit #129 Etappe 2 in 6371576 — damit ist sie auch im Browser belegt.
Was umgesetzt ist
Der Schlüssel steht, wo der Dateiname steht.TextureRef.file darf jetzt auch gen:<assetId>@<revision> sein — eine hochgeladene Datei ist <sha256>.<ext> und kann nie so aussehen. Alles, was einen Ref nur weiterreicht (Sprite, Tile, Tilemap-Mesh, Partikelquelle, Shader, Seitenverhältnis), ist unverändert, weil es weiter eine Texture sieht.
Eine Abweichung vom Plan im Issue: die Revision ist ein String, kein Zähler.gen:a1@7 und gen:a1@c0ffee gehen durch dieselbe Tür. Das kostet nichts und erlaubt einem Erzeuger, seinen eigenen Inhalt zu fingerprinten, statt sich darauf zu verlassen, dass jedes Command das Hochzählen nicht vergisst. #129 nutzt genau das.
Zwei Register, beide nach Asset-Typ als blankem String, damit ein Plugin einen eigenen Typ mitbringen darf:
registerPictureSource(type, read) in core/model/generated.ts sagt dem Modell Leinwandgröße und Revision; resolvePicture() baut daraus den Ref — ohne Rechteck, ohne Slice, smooth aus props.filter wie bei einer Textur.
registerPictureGenerator(type, draw) in viewport/generatedPictures.ts liefert die fertige Textur; projectPictureFactory(() => state.project) verbindet beides pro Sitzung in App.ts.
Die Invalidierung ist eine Regel statt Rätselraten an zwanzig Aufrufstellen.GeneratedPictures<T> hält ein Bild pro Asset, gekeyt auf die Revision, und gibt das alte frei, sobald das neue gezeichnet ist; die TextureLibrary zerstört es und wirft die davon geschnittenen Crops mit weg. Ein Erzeuger, der noch nichts liefern kann, wird nicht gemerkt, sondern nächstes Mal wieder gefragt. Die Klasse importiert Pixi nur als Typ — die ganze Buchführung ist ohne GPU messbar.
Mitgezogen: der Bild-Picker (ein gezeichnetes Asset steht nach Namen zwischen den Texturen, ohne Frames), die Lösch-Warnung (zählt Tiles und Animationen auch dafür) und das Asset-Kontextmenü, wo Create sprite from picture es jetzt abdeckt, während Create animation from texture texture-only bleibt, weil es Frames braucht.
Ein Fund beim Sondieren:crop() hätte eine erzeugte Textur gar nicht schneiden können — eine RenderTexture hat kein source.resource zum Zurückzeichnen, drawImage wäre auf null gelaufen. Sie wird jetzt ganz herausgegeben; ein gezeichnetes Bild hat ohnehin kein Sheet, dessen Nachbarzeile durchscheinen könnte.
Tests
./check.sh → 124 Files, 1817 Tests, CHECK EXIT 0 (vorher 122 / 1799)
npm run build → BUILD EXIT 0
GM_STEPS=1-15,25,28,31,37,39,40 → ALL PASSED – 654 checks
GM_STEPS=1-13,39 (nach der Menü-Änderung, neu gebaut) → ALL PASSED – 429 checks
18 neue Unit-Tests: 10 in tests/core/generated-picture.test.ts (Schlüssel bauen und zerlegen, eine hochgeladene Datei ist keiner, kein Register → kein Bild, Auflösung auf Leinwandgröße, neue Revision = neuer Schlüssel, Leinwand ohne Größe, Filter, Sprite-Maß, Picker-Reihenfolge) und 8 in tests/viewport/generated-pictures.test.ts (einmal zeichnen, alte Revision freigeben und nur die des eigenen Assets, null wird nicht gemerkt, kein Schlüssel = nichts gezeichnet, Sitzungswechsel gibt alles zurück, peek).
Drei Gegenproben, alle greifen: erzeugte Assets aus resolvePicture nehmen → 5 der 10 Kern-Tests fallen; die alte Revision nicht freigeben → 1 Viewport-Test fällt; den Picker wieder auf Texturen einengen → 3 Tests fallen. Originale per cmp zurückgeprüft.
Keine neuen e2e-Checks in diesem Commit, Check-Summe blieb bei 1157 — ohne Erzeuger ist die Grundlage im Browser nicht sichtbar. Der e2e-Beleg kam wie angekündigt mit #129 (Modul 47).
Bewusst nicht gebaut
Zwischenspeichern (IndexedDB) — so stand es als Vorschlag im Issue: erst, wenn es messbar stört. Nach einem Reload wird neu gerastert.
Doku: Wiki Textures (neuer Abschnitt A picture can be drawn instead of uploaded — der Vertrag und die zwei Unterschiede: keine Frames, nichts gespeichert) und Architecture (core/model/generated, viewport/generatedPictures).
Gebaut und gepusht: `cb1804d` (10 Dateien, 6 geändert, 4 neu), Wiki `dfecee8`. Der Erzeuger, der die Grundlage sichtbar macht, kam mit #129 Etappe 2 in `6371576` — damit ist sie auch im Browser belegt.
## Was umgesetzt ist
**Der Schlüssel steht, wo der Dateiname steht.** `TextureRef.file` darf jetzt auch `gen:<assetId>@<revision>` sein — eine hochgeladene Datei ist `<sha256>.<ext>` und kann nie so aussehen. Alles, was einen Ref nur weiterreicht (Sprite, Tile, Tilemap-Mesh, Partikelquelle, Shader, Seitenverhältnis), ist unverändert, weil es weiter eine `Texture` sieht.
**Eine Abweichung vom Plan im Issue: die Revision ist ein String, kein Zähler.** `gen:a1@7` und `gen:a1@c0ffee` gehen durch dieselbe Tür. Das kostet nichts und erlaubt einem Erzeuger, seinen eigenen Inhalt zu fingerprinten, statt sich darauf zu verlassen, dass jedes Command das Hochzählen nicht vergisst. #129 nutzt genau das.
**Zwei Register, beide nach Asset-Typ als blankem String**, damit ein Plugin einen eigenen Typ mitbringen darf:
- `registerPictureSource(type, read)` in `core/model/generated.ts` sagt dem Modell Leinwandgröße und Revision; `resolvePicture()` baut daraus den Ref — ohne Rechteck, ohne Slice, `smooth` aus `props.filter` wie bei einer Textur.
- `registerPictureGenerator(type, draw)` in `viewport/generatedPictures.ts` liefert die fertige Textur; `projectPictureFactory(() => state.project)` verbindet beides pro Sitzung in `App.ts`.
**Die Invalidierung ist eine Regel statt Rätselraten an zwanzig Aufrufstellen.** `GeneratedPictures<T>` hält ein Bild pro Asset, gekeyt auf die Revision, und gibt das alte frei, sobald das neue gezeichnet ist; die `TextureLibrary` zerstört es und wirft die davon geschnittenen Crops mit weg. Ein Erzeuger, der noch nichts liefern kann, wird nicht gemerkt, sondern nächstes Mal wieder gefragt. Die Klasse importiert Pixi nur als Typ — die ganze Buchführung ist ohne GPU messbar.
**Mitgezogen:** der Bild-Picker (ein gezeichnetes Asset steht nach Namen zwischen den Texturen, ohne Frames), die Lösch-Warnung (zählt Tiles und Animationen auch dafür) und das Asset-Kontextmenü, wo *Create sprite from picture* es jetzt abdeckt, während *Create animation from texture* texture-only bleibt, weil es Frames braucht.
**Ein Fund beim Sondieren:** `crop()` hätte eine erzeugte Textur gar nicht schneiden können — eine RenderTexture hat kein `source.resource` zum Zurückzeichnen, `drawImage` wäre auf `null` gelaufen. Sie wird jetzt ganz herausgegeben; ein gezeichnetes Bild hat ohnehin kein Sheet, dessen Nachbarzeile durchscheinen könnte.
## Tests
```
./check.sh → 124 Files, 1817 Tests, CHECK EXIT 0 (vorher 122 / 1799)
npm run build → BUILD EXIT 0
GM_STEPS=1-15,25,28,31,37,39,40 → ALL PASSED – 654 checks
GM_STEPS=1-13,39 (nach der Menü-Änderung, neu gebaut) → ALL PASSED – 429 checks
```
18 neue Unit-Tests: 10 in `tests/core/generated-picture.test.ts` (Schlüssel bauen und zerlegen, eine hochgeladene Datei ist keiner, kein Register → kein Bild, Auflösung auf Leinwandgröße, neue Revision = neuer Schlüssel, Leinwand ohne Größe, Filter, Sprite-Maß, Picker-Reihenfolge) und 8 in `tests/viewport/generated-pictures.test.ts` (einmal zeichnen, alte Revision freigeben und nur die des eigenen Assets, `null` wird nicht gemerkt, kein Schlüssel = nichts gezeichnet, Sitzungswechsel gibt alles zurück, peek).
Drei Gegenproben, alle greifen: erzeugte Assets aus `resolvePicture` nehmen → 5 der 10 Kern-Tests fallen; die alte Revision nicht freigeben → 1 Viewport-Test fällt; den Picker wieder auf Texturen einengen → 3 Tests fallen. Originale per `cmp` zurückgeprüft.
Keine neuen e2e-Checks in diesem Commit, Check-Summe blieb bei 1157 — ohne Erzeuger ist die Grundlage im Browser nicht sichtbar. Der e2e-Beleg kam wie angekündigt mit #129 (Modul 47).
## Bewusst nicht gebaut
**Zwischenspeichern (IndexedDB)** — so stand es als Vorschlag im Issue: erst, wenn es messbar stört. Nach einem Reload wird neu gerastert.
Doku: Wiki *Textures* (neuer Abschnitt *A picture can be drawn instead of uploaded* — der Vertrag und die zwei Unterschiede: keine Frames, nichts gespeichert) und *Architecture* (`core/model/generated`, `viewport/generatedPictures`).
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Vorgezogen aus #129 (Frage 12, Antwort: „gemeinsame Grundlage vorziehen"). Diese Stelle brauchen #129 (Vector Art) und #130 (Pixel Editor) gleichermaßen — sie soll genau einmal gebaut werden.
Befund
Ein Bild ist heute immer eine Datei auf dem Server:
TextureRef.fileist ein Dateiname,isTextureFile()prüft Hash + Endung (src/core/model/texture.ts).TextureLibrary.load()holt diesen Namen per HTTP.resolvePicture()(src/core/model/texture.ts:86) ist der eine Weg, über den Sprite, Tile, Tilemap, Partikelquelle und der Bild-Picker an ein Bild kommen.Ein erzeugtes Bild — gerastert aus Vector Art, gemalt im Pixel Editor — hat keine Datei. Ohne diese Grundlage müsste jeder Erzeuger seinen eigenen Sonderweg durch den Bildpfad bauen.
Zu bauen (Vorschlag)
filebleibt der Schlüssel, darf aber auch ein erzeugter sein, Formgen:<assetId>@<rev>.TextureLibrary.load()fragt für so einen Schlüssel ein Register von Erzeugern statt den Server. Zuschnitt, Filter-Modus, Shader, Tilemap-Mesh und Partikel sehen weiterhin eineTextureund bleiben unverändert.resolvePicture()liefert für ein erzeugendes Asset einen Ref mit der Leinwandgröße alssourceWidth/Height, damitproperties/aspect.tsohne Sonderfall die Sprite-Größe bestimmt.usages.ts) nehmen den neuen Asset-Typ mit.Tests
resolvePicture()auf ein erzeugendes Asset, Erzeuger-Register ohne GPU (Fake-Erzeuger).Offen
richtig, zwischenspeichern kann man weglassen
Gebaut und gepusht:
cb1804d(10 Dateien, 6 geändert, 4 neu), Wikidfecee8. Der Erzeuger, der die Grundlage sichtbar macht, kam mit #129 Etappe 2 in6371576— damit ist sie auch im Browser belegt.Was umgesetzt ist
Der Schlüssel steht, wo der Dateiname steht.
TextureRef.filedarf jetzt auchgen:<assetId>@<revision>sein — eine hochgeladene Datei ist<sha256>.<ext>und kann nie so aussehen. Alles, was einen Ref nur weiterreicht (Sprite, Tile, Tilemap-Mesh, Partikelquelle, Shader, Seitenverhältnis), ist unverändert, weil es weiter eineTexturesieht.Eine Abweichung vom Plan im Issue: die Revision ist ein String, kein Zähler.
gen:a1@7undgen:a1@c0ffeegehen durch dieselbe Tür. Das kostet nichts und erlaubt einem Erzeuger, seinen eigenen Inhalt zu fingerprinten, statt sich darauf zu verlassen, dass jedes Command das Hochzählen nicht vergisst. #129 nutzt genau das.Zwei Register, beide nach Asset-Typ als blankem String, damit ein Plugin einen eigenen Typ mitbringen darf:
registerPictureSource(type, read)incore/model/generated.tssagt dem Modell Leinwandgröße und Revision;resolvePicture()baut daraus den Ref — ohne Rechteck, ohne Slice,smoothausprops.filterwie bei einer Textur.registerPictureGenerator(type, draw)inviewport/generatedPictures.tsliefert die fertige Textur;projectPictureFactory(() => state.project)verbindet beides pro Sitzung inApp.ts.Die Invalidierung ist eine Regel statt Rätselraten an zwanzig Aufrufstellen.
GeneratedPictures<T>hält ein Bild pro Asset, gekeyt auf die Revision, und gibt das alte frei, sobald das neue gezeichnet ist; dieTextureLibraryzerstört es und wirft die davon geschnittenen Crops mit weg. Ein Erzeuger, der noch nichts liefern kann, wird nicht gemerkt, sondern nächstes Mal wieder gefragt. Die Klasse importiert Pixi nur als Typ — die ganze Buchführung ist ohne GPU messbar.Mitgezogen: der Bild-Picker (ein gezeichnetes Asset steht nach Namen zwischen den Texturen, ohne Frames), die Lösch-Warnung (zählt Tiles und Animationen auch dafür) und das Asset-Kontextmenü, wo Create sprite from picture es jetzt abdeckt, während Create animation from texture texture-only bleibt, weil es Frames braucht.
Ein Fund beim Sondieren:
crop()hätte eine erzeugte Textur gar nicht schneiden können — eine RenderTexture hat keinsource.resourcezum Zurückzeichnen,drawImagewäre aufnullgelaufen. Sie wird jetzt ganz herausgegeben; ein gezeichnetes Bild hat ohnehin kein Sheet, dessen Nachbarzeile durchscheinen könnte.Tests
18 neue Unit-Tests: 10 in
tests/core/generated-picture.test.ts(Schlüssel bauen und zerlegen, eine hochgeladene Datei ist keiner, kein Register → kein Bild, Auflösung auf Leinwandgröße, neue Revision = neuer Schlüssel, Leinwand ohne Größe, Filter, Sprite-Maß, Picker-Reihenfolge) und 8 intests/viewport/generated-pictures.test.ts(einmal zeichnen, alte Revision freigeben und nur die des eigenen Assets,nullwird nicht gemerkt, kein Schlüssel = nichts gezeichnet, Sitzungswechsel gibt alles zurück, peek).Drei Gegenproben, alle greifen: erzeugte Assets aus
resolvePicturenehmen → 5 der 10 Kern-Tests fallen; die alte Revision nicht freigeben → 1 Viewport-Test fällt; den Picker wieder auf Texturen einengen → 3 Tests fallen. Originale percmpzurückgeprüft.Keine neuen e2e-Checks in diesem Commit, Check-Summe blieb bei 1157 — ohne Erzeuger ist die Grundlage im Browser nicht sichtbar. Der e2e-Beleg kam wie angekündigt mit #129 (Modul 47).
Bewusst nicht gebaut
Zwischenspeichern (IndexedDB) — so stand es als Vorschlag im Issue: erst, wenn es messbar stört. Nach einem Reload wird neu gerastert.
Doku: Wiki Textures (neuer Abschnitt A picture can be drawn instead of uploaded — der Vertrag und die zwei Unterschiede: keine Frames, nichts gespeichert) und Architecture (
core/model/generated,viewport/generatedPictures).