From e702517662e0e4f3567a1647879701f65910e35c Mon Sep 17 00:00:00 2001 From: KNSO Date: Sun, 13 Sep 2026 12:27:39 +0200 Subject: [PATCH] docs(minimap): Landeplatz auf ganzem Kollisionsboden, Fokus, Trackpad-Zoom und Ladefehler nach dem Abschluss-Review Co-Authored-By: Claude Opus 5 --- .../specs/2026-09-13-minimap-design.md | 35 ++++++++++--------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/docs/superpowers/specs/2026-09-13-minimap-design.md b/docs/superpowers/specs/2026-09-13-minimap-design.md index 6973917..467c4c1 100644 --- a/docs/superpowers/specs/2026-09-13-minimap-design.md +++ b/docs/superpowers/specs/2026-09-13-minimap-design.md @@ -16,7 +16,7 @@ Eine große Kartenansicht des gesamten Vice-City-Ausschnitts, die sich über dem | Inhalt | Eigene Position mit Blickrichtung, Fahrzeuge, Zoomen und Verschieben, Klick zum Hinspringen. **Keine** Wahrzeichen. | | Kartenbild | Vorab erzeugt, 1 Pixel je Meter. | | Welt während offener Karte | Läuft weiter, keine Pause. | -| Klick auf Gebäude oder Wasser | Landung auf dem nächsten begehbaren Punkt im Umkreis von 20 m, sonst Hinweis und kein Sprung. | +| Klick auf Gebäude oder Wasser | Landung in der Mitte des nächsten begehbaren Maskenpixels im Umkreis von 20 m, sonst Hinweis und kein Sprung. | | Chunks | Raster auf der Karte sichtbar, jeder Chunk benannt als **Planquadrat A1 bis L12**. Zweck: der Nutzer spricht Chunks im Chat für die Feinkalibrierung an. | ## Daten @@ -26,7 +26,7 @@ Drei neue Dateien neben `manifest.json` in `public/data/vice-a/`: | Datei | Inhalt | |---|---| | `map.png` | 3.000 × 3.000 px, 1 px/m. Wasser, Land, Sand, Park, Stadtfläche, Straßen und Brücken, Gebäude nach Höhe getönt — dieselben Farben wie die Gesamtansicht. **Ohne Palmen**: bei 1 px/m wären sie nur Rauschen. Löcher werden wie in der Gesamtansicht über eine Maske korrekt ausgespart. | -| `map-walkable.png` | 750 × 750 px, 4 m/px, Graustufen. 255 = begehbar (Land, Straße, Brücke, Sand, Park, Stadtfläche außerhalb von Gebäuden), 0 = nicht begehbar (Wasser oder Gebäude). | +| `map-walkable.png` | 750 × 750 px, 4 m/px, Graustufen. 255 = begehbar: die **ganze** 4 × 4-m-Fläche ist Kollisionsboden — Landplatte oder Brücke — ohne Gebäude, aus einem 1-m-Raster verdichtet. 0 = sonst. Straßen und Beläge zählen nur über der Landplatte, weil sie selbst reine Auflagen ohne Kollision sind. (Nach dem Abschluss-Review verschärft: die frühere großzügige Maske setzte die Figur bei 4,3 % der Klicks auf Gebäude in eine Wand oder ins Wasser.) | | `map.json` | `{ "scale": 1.0, "walkable_scale": 0.25, "slice": { "x0": -1000, "x1": 2000, "y0": -500, "y1": 2500 }, "manifest_sha256": "" }` | `map.json` ist die **einzige** Quelle für Maßstab und Ausschnittsgrenzen der Karte. Der Browser liest sie, statt die Werte ein zweites Mal im JavaScript festzuschreiben. @@ -92,7 +92,7 @@ name(i, j) = "ABCDEFGHIJKL"[i] + (j + 1) **Auf der Karte:** - Rasterlinien an allen Chunk-Grenzen. - Der Name steht in der Mitte jedes Chunks, sobald der Chunk auf dem Bildschirm mindestens 40 px breit ist; darunter nur die Linien. Beim Öffnen passt der ganze Ausschnitt ins Fenster, dann ist jeder Chunk auf gängigen Bildschirmen breit genug. -- Eine feste Anzeige unten rechts nennt Chunk-Name und Weltkoordinate der Stelle unter dem Mauszeiger, etwa `F3 · x 412 / y 1830`. Die Koordinate erlaubt, eine Stelle innerhalb eines Chunks genau zu benennen. +- Eine feste Anzeige oben links unter der Standortanzeige nennt Chunk-Name und Weltkoordinate der Stelle unter dem Mauszeiger, etwa `F3 · x 412 / y 1830`. Die Koordinate erlaubt, eine Stelle innerhalb eines Chunks genau zu benennen. - Der Chunk, in dem der Spieler gerade steht, ist hervorgehoben und sein Name steht fest in einer Ecke der Karte. ## Anbindung an vorhandene Funktionen @@ -104,7 +104,7 @@ name(i, j) = "ABCDEFGHIJKL"[i] + (j + 1) ## Bedienung -- **Öffnen und Schließen:** M öffnet und schließt, Esc schließt. M wird ignoriert, solange ein Eingabefeld (`input`, `textarea`, `select`) den Fokus hat — sonst öffnete sich die Karte beim Eintippen eines Werts im Panel. +- **Öffnen und Schließen:** M öffnet und schließt, Esc schließt. M wird ignoriert, solange ein Textfeld (Text- oder Zahleneingabe, `textarea`, editierbarer Inhalt) den Fokus hat — sonst öffnete sich die Karte beim Eintippen eines Werts im Panel. Hat eine Auswahlliste, ein Kontrollkästchen oder ein Farbwähler den Fokus, öffnet M die Karte, bricht die Standardaktion ab und löst den Fokus — sonst wählte eine Auswahlliste per Tipp-Suche einen Eintrag, etwa ein Wahrzeichen, und spränge dorthin. Bei offener Karte wandert Tab nicht ins verdeckte Panel. - **Beim Öffnen** immer der ganze Ausschnitt, eingepasst ins Fenster. - **Mausfang:** beim Öffnen wird der Pointer-Lock gelöst. Beim Schließen wird er nicht automatisch neu angefordert; Browser erlauben das nur nach einer Nutzergeste, ein Klick ins Spiel fängt ihn wie gewohnt. - **Steuerung ruht, Welt läuft.** Solange die Karte offen ist, erreichen Tastatur- und Mausereignisse die Figur nicht; Streaming, Fahrzeugphysik und Animation laufen weiter. @@ -114,25 +114,25 @@ name(i, j) = "ABCDEFGHIJKL"[i] + (j + 1) - Die Listener bleiben dauerhaft registriert statt beim Öffnen hinzugefügt und beim Schließen entfernt zu werden: M muss bei geschlossener Karte ohnehin gehört werden, und ein vergessenes Entfernen kann so nicht passieren. - Beim Öffnen wird einmal `window.dispatchEvent(new Event("blur"))` ausgelöst. Das ruft im Controller `resetKeys()` auf, damit eine beim Drücken von M gehaltene Bewegungstaste nicht hängen bleibt und die Figur weiterlaufen lässt. - **Nicht** `unbindEvents()` verwenden: es setzt `isupdate = false` und hält damit die gesamte Figurenaktualisierung an, nicht nur die Eingabe. Das widerspräche der Entscheidung, dass die Welt weiterläuft. -- **Zoomen:** Mausrad, um den Mauszeiger herum, von „ganzer Ausschnitt passt ins Fenster" bis vierfach. +- **Zoomen:** Mausrad oder Trackpad, um den Mauszeiger herum, von „ganzer Ausschnitt passt ins Fenster" bis vierfach. Der Zoomfaktor wächst mit der Scrollstrecke (ein Rastschritt von etwa 100 px zoomt um rund 1,16×), damit die vielen kleinen Ereignisse einer Trackpad-Geste fließend zoomen; Zeilen, Seiten und die Pinch-Geste werden wie im Controller umgerechnet. - **Verschieben:** Ziehen mit gedrückter Maustaste, begrenzt auf den Kartenrand. -- **Hinspringen:** Ein Klick — Maustaste gedrückt und losgelassen mit weniger als 4 px Bewegung dazwischen — springt an die Stelle und schließt die Karte. Mit 4 px oder mehr Bewegung ist es ein Ziehen, und es wird nicht gesprungen. -- **Landeplatz:** Der Klickpunkt wird in `map-walkable.png` nachgeschlagen. - - Begehbar: Sprung genau dorthin. - - Nicht begehbar (Gebäude oder Wasser): Sprung auf den nächsten begehbaren Pixel im Umkreis von 20 m, gemessen als euklidischer Abstand. In Maskenpixeln sind das `20 · walkable_scale = 5` px — `nearestWalkable` erhält den Radius in Pixeln, die Umrechnung aus Metern geschieht an genau einer Stelle über `map.json`. +- **Hinspringen:** Ein Klick — Maustaste gedrückt und losgelassen, und der Zeiger war zu keinem Zeitpunkt dazwischen 4 px oder weiter vom Startpunkt entfernt — springt an die Stelle und schließt die Karte. Sonst ist es ein Ziehen, auch wenn der Zeiger zum Startpunkt zurückkehrt, und es wird nicht gesprungen. +- **Landeplatz:** Der Klickpunkt wird in `map-walkable.png` nachgeschlagen. Gelandet wird immer in der **Mitte** eines begehbaren Pixels, nie am Klickpunkt selbst: weil die Maske einen Pixel nur freigibt, wenn seine ganze Fläche Kollisionsboden ist, sind es von der Mitte aus mindestens 2 m bis zu Wand oder Wasser. Die Abweichung vom Klickpunkt beträgt höchstens 2,8 m. + - Begehbar: Sprung in die Mitte dieses Pixels. + - Nicht begehbar (Gebäude, Wasser oder nur teilweise freier Rand): Sprung in die Mitte des nächsten begehbaren Pixels im Umkreis von 20 m, gemessen als euklidischer Abstand. In Maskenpixeln sind das `20 · walkable_scale = 5` px — `nearestWalkable` erhält den Radius in Pixeln, die Umrechnung aus Metern geschieht an genau einer Stelle über `map.json`. - Kein begehbarer Pixel im Umkreis: kein Sprung, die Karte bleibt offen, und für etwa zwei Sekunden erscheint „Kein Landeplatz". ## Fehlerfälle | Fall | Verhalten | |---|---| -| `map.png` lässt sich nicht laden | M öffnet die Überlagerung mit dem Hinweis „Kartenbild fehlt — `uv run python overview.py` ausführen". Kein Absturz, das Spiel läuft weiter. | -| `map-walkable.png` lässt sich nicht laden | Anschauen, Zoomen und Verschieben funktionieren. Klick zum Hinspringen ist abgeschaltet, ein Hinweis nennt den Grund. | +| `map.png` lässt sich nicht laden oder passt nicht zu `map.json` | M öffnet die Überlagerung mit dem Hinweis „Kartenbild fehlt — `uv run python overview.py` ausführen". Kein Absturz, das Spiel läuft weiter. Erneutes Öffnen lädt neu, ohne die Seite neu zu laden. Ein unerwarteter Fehler beim Zeichnen führt zum selben Hinweis statt zu einer stehenden Karte. | +| `map-walkable.png` lässt sich nicht laden oder hat nicht die Größe aus `map.json` | Anschauen, Zoomen und Verschieben funktionieren. Klick zum Hinspringen ist abgeschaltet, ein Hinweis nennt den Grund. Erneutes Öffnen lädt neu. | | `map.json` fehlt oder ist ungültig | Wie fehlendes Kartenbild: ohne Maßstab und Grenzen lässt sich nichts korrekt platzieren. | -| `manifest_sha256` passt nicht zum geladenen Manifest | Die Karte funktioniert vollständig, zeigt aber sichtbar „Karte veraltet — `overview.py` erneut ausführen". | +| `manifest_sha256` passt nicht zum geladenen Manifest | Die Karte funktioniert vollständig, zeigt aber sichtbar „Karte veraltet — `overview.py` erneut ausführen und Seite neu laden". | | Kein sicherer Kontext (Aufruf über eine LAN-Adresse statt `localhost`) | `crypto.subtle` fehlt dort. Die Veraltungsprüfung entfällt still, alles andere funktioniert. | -**Grenze der Veraltungsprüfung:** Der Fingerabdruck erkennt jede Änderung am Manifest — Klassenanteile, Chunk-Liste, Namensnennung. Eine Änderung, die nur Chunk-Inhalte betrifft, ohne das Manifest zu berühren, bleibt unerkannt; genau so eine war die Mindestbreite für Gebäude. Diese Lücke schließt sich erst, wenn die Kartenerzeugung in `run.py` hängt und damit gar nicht mehr veralten kann. +**Grenze der Veraltungsprüfung:** Der Fingerabdruck erkennt jede Änderung am Manifest — Klassenanteile, Chunk-Liste, Namensnennung. Eine Änderung, die nur Chunk-Inhalte betrifft, ohne das Manifest zu berühren, bleibt unerkannt; genau so eine war die Mindestbreite für Gebäude. Seit dem Abschluss-Review fängt ein pytest diese Lücke beim Testlauf: er erzeugt Kartenbild, Maske und `map.json` frisch aus den ausgelieferten Chunks und vergleicht sie mit den ausgelieferten Dateien. Ganz schließt sie sich, wenn die Kartenerzeugung in `run.py` hängt und damit gar nicht mehr veralten kann. ## Namensnennung @@ -158,16 +158,17 @@ Die Namensnennung steht unten links; die Koordinatenanzeige der Karte steht desh **Ohne Browser (vitest), für `mapMath.js`:** - `worldToMap` und `mapToWorld` an bekannten Punkten: Spawn `(1699, 3, −743)` und alle vier Ecken des Ausschnitts; dazu Hin- und Rückweg ergibt wieder den Ausgangspunkt. Ein Test, der eine gespiegelte Achse sicher fängt. - Zoomgrenzen: nie kleiner als „ganzer Ausschnitt", nie größer als vierfach; Verschiebung bleibt innerhalb des Kartenrands. -- Klick gegen Ziehen an der 4-px-Schwelle, knapp darunter und knapp darüber. -- `nearestWalkable` an einer künstlichen Maske: begehbarer Treffer bleibt, wo er ist; ein Gebäudepixel neben einer Straße findet die Straße; nur Wasser im Umkreis ergibt kein Ergebnis; ein Treffer knapp außerhalb von 20 m zählt nicht. +- Klick gegen Ziehen an der 4-px-Schwelle, knapp darunter und knapp darüber, und ein Ziehen, das zum Startpunkt zurückkehrt. +- `nearestWalkable` an einer künstlichen Maske: begehbarer Treffer landet in der Mitte seines Pixels; ein Gebäudepixel neben einer Straße findet die Straße; nur Wasser im Umkreis ergibt kein Ergebnis; ein Treffer knapp außerhalb von 20 m zählt nicht. **Pipeline (pytest), für `overview.py`:** - `map.png` hat 3.000 × 3.000 px; ein bekannter Wasserpunkt in der Bucht ist wasserfarben, ein bekannter Landpunkt nicht. -- `map-walkable.png` hat 750 × 750 px; die Fläche eines Gebäudegrundrisses ist 0, eine Straße daneben 255. +- `map-walkable.png` hat 750 × 750 px; die Fläche eines Gebäudegrundrisses ist 0, eine Straße daneben 255; Straße und Belag ohne Landplatte sind 0, eine Brücke 255; ein Pixel, den Wasser oder ein Gebäude nur anschneidet, ist 0. +- Die ausgelieferten Kartendateien entsprechen einer frischen Erzeugung aus den ausgelieferten Chunks. - `map.json` enthält den korrekten SHA-256 des aktuellen Manifests. **Im Browser, mit Bildern im Bericht:** -- M öffnet, M und Esc schließen; bei fokussiertem Eingabefeld öffnet M nicht. +- M öffnet, M und Esc schließen; bei fokussiertem Textfeld öffnet M nicht; bei fokussierter Auswahlliste öffnet M die Karte, ohne einen Eintrag zu wählen. - Beim Öffnen mit gehaltener W-Taste bleibt die Figur stehen, statt weiterzulaufen. - Zoomen, Verschieben, Kartenrand. - Klick auf eine Straße, auf ein Gebäude (Landung daneben) und mitten in die Bucht („Kein Landeplatz").