fix(ui): Landeplatz nur auf ganzem Kollisionsboden, Trackpad-Zoom, Fokus und Ladefehler der Minimap

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
knsoandClaude Sonnet 5 committed 2026-09-13 12:47:51 +02:00
1 parent e702517662
commit dd5ced505b
9 files changed
+263 -54

No files matched your search

+32 -9
View File
@@ -15,6 +15,7 @@ import hashlib
import json
from pathlib import Path
import numpy as np
from PIL import Image, ImageDraw
from leonida.coords import SLICE
@@ -78,8 +79,9 @@ def _paint_layers(img, chunks, scale, *, land, surface, road, bridge, building,
"""Ebene für Ebene über alle Chunks, damit nichts von einem Nachbarchunk überdeckt wird.
surface ist eine Funktion der Belagsart, building eine Funktion der Gebäudehöhe.
palm=None zeichnet keine Palmen. Reihenfolge und Füllung entsprechen exakt dem
bisherigen Prüfbild, damit dessen Ausgabe bytegleich bleibt.
palm=None zeichnet keine Palmen; eine Füllung None lässt Beläge oder Straßen aus.
Reihenfolge und Füllung entsprechen exakt dem bisherigen Prüfbild, damit dessen
Ausgabe bytegleich bleibt.
"""
stats = {"land": 0, "strassen": 0, "bruecken": 0, "gebaeude": 0, "palmen": 0}
placed = [(chunk, chunk["origin"][0], -chunk["origin"][1]) for chunk in chunks]
@@ -89,10 +91,14 @@ def _paint_layers(img, chunks, scale, *, land, surface, road, bridge, building,
stats["land"] += 1
for chunk, ox, oy in placed:
for item in chunk["surfaces"]:
_draw(img, item["poly"], ox, oy, surface(item["kind"]), scale=scale)
fill = surface(item["kind"])
if fill is not None:
_draw(img, item["poly"], ox, oy, fill, scale=scale)
for chunk, ox, oy in placed:
for item in chunk["roads"]:
_draw(img, item["poly"], ox, oy, bridge if item["bridge"] else road, scale=scale)
fill = bridge if item["bridge"] else road
if fill is not None:
_draw(img, item["poly"], ox, oy, fill, scale=scale)
stats["bruecken" if item["bridge"] else "strassen"] += 1
for chunk, ox, oy in placed:
for item in chunk["buildings"]:
@@ -117,6 +123,27 @@ def render(out_dir: Path, dst: Path) -> dict:
return stats
def _walkable_mask(chunks: list[dict], width_m: int) -> Image.Image:
"""Begehbarkeitsmaske mit WALKABLE_SCALE, vorsichtig aus einem 1-m-Raster verdichtet.
Begehbar heißt: die Figur steht dort auf Kollision. Die tragen nur die Landplatte und die
Brücken — Straßen und Beläge sind reine Auflagen (DECALS in src/world/streaming.js) —, und
Gebäude sind geschlossene Kästen. Ein Maskenpixel gilt nur als begehbar, wenn jeder
Quadratmeter darunter frei ist. Ein nur teilweise freier Pixel setzte die Figur sonst in eine
Wand oder vor die Kaimauer ins Wasser; an den ausgelieferten Daten traf das 4,3 % der Klicks
auf Gebäude. Die Landung erfolgt in der Pixelmitte (nearestWalkable in src/ui/mapMath.js),
also mindestens 2 m von jedem gesperrten Quadratmeter entfernt.
"""
fine = Image.new("L", (width_m, width_m), BLOCKED)
_paint_layers(fine, chunks, 1.0, land=WALKABLE, surface=lambda _kind: None, road=None,
bridge=WALKABLE, building=lambda _height: BLOCKED)
step = round(1 / WALKABLE_SCALE)
size = width_m // step
cells = np.asarray(fine).reshape(size, step, size, step)
free = (cells == WALKABLE).all(axis=(1, 3))
return Image.fromarray(np.where(free, WALKABLE, BLOCKED).astype(np.uint8))
def render_map(out_dir: Path, dst_dir: Path) -> dict:
"""Kartenbild, Begehbarkeitsmaske und map.json für die Minimap."""
chunks = _load_chunks(out_dir)
@@ -126,11 +153,7 @@ def render_map(out_dir: Path, dst_dir: Path) -> dict:
_paint_layers(picture, chunks, MAP_SCALE, land=LAND, surface=SURFACE.__getitem__, road=ROAD,
bridge=BRIDGE, building=_building_shade, outline=OUTLINE)
# Begehbar ist, was Boden hat: Land, Beläge, Straßen und Brücken. Gebäude werden danach wieder
# gesperrt; Wasser ist der Hintergrund. Innenhöfe bleiben über die Löcher begehbar.
walkable = Image.new("L", (int(width_m * WALKABLE_SCALE),) * 2, BLOCKED)
_paint_layers(walkable, chunks, WALKABLE_SCALE, land=WALKABLE, surface=lambda _kind: WALKABLE,
road=WALKABLE, bridge=WALKABLE, building=lambda _height: BLOCKED)
walkable = _walkable_mask(chunks, width_m)
dst_dir.mkdir(parents=True, exist_ok=True)
picture.save(dst_dir / "map.png")