{ "APP": { "TITLE": "Playground", "COPYRIGHT": "Alle Rechte vorbehalten.", "AI_NOTICE": "Entstanden mit einem KI-gestützten Engineering-Workflow.", "AI_NOTICE_LINK": "Wie ich mit KI arbeite" }, "TOPBAR": { "ABOUT": "Über mich", "IMPRINT": "Impressum", "PROJECTS": "Projekte", "ALGORITHMS": "Algorithmen", "STOPWATCH": "Stoppuhr", "SETTINGS": "Einstellungen", "LANGUAGE": "Sprache", "APPEARANCE": "Darstellung" }, "THEME": { "LIGHT": "Hell", "DARK": "Dunkel" }, "LANG": { "LABEL": "Sprache", "EN": "Englisch", "DE": "Deutsch" }, "ABOUT": { "ALT": { "PROFILE": "Profilfoto von Andreas Dahm" }, "HELLO": "Hallo, ich bin Andreas.", "LEAD": "Ich arbeite seit 2010 als Softwareentwickler, die meiste Zeit davon in zwei recht unterschiedlichen Bereichen. Der eine ist Echtzeit-3D-Simulation: Stoffphysik, Kollisionserkennung, GPU-Shader. Der andere ist Cloud-Architektur für größere Unternehmensplattformen. Ein paar Jahre habe ich ein kleines Backend-Team geleitet, den Umbau eines großen Legacy-Monolithen Richtung Microservices geplant und zusammen mit meinem Bruder ein Spiel auf Steam veröffentlicht. Zuletzt stecke ich viel Zeit in Tooling rund um KI-gestützte Entwicklung, vor allem in die Frage, wie das Ergebnis überprüfbar bleibt. Privat baue ich Spiele und probiere Sachen mit Algorithmen aus. Das meiste davon landet irgendwann auf dieser Seite.", "ROLE": "Senior-Softwareentwickler · Cloud-Architektur & 3D-Simulation", "LOCATION": "München · Remote", "VIEW_PROJECTS": "Projekte ansehen", "SECTION": { "SKILLS": "Fähigkeiten & Stack", "LANGUAGES": "Sprachen & Frameworks", "ARCH_CLOUD": "Architektur & Cloud", "SIM_ALGO": "Simulation & Algorithmen", "AI_ENG": "KI-gestütztes Engineering", "EXPERIENCE": "Erfahrung", "PROJECTS": "Projekte", "EDUCATION": "Ausbildung", "SHOW_EARLIER_XP": "Frühere Stationen anzeigen", "SHOW_EARLIER_EDU": "Frühere Ausbildung anzeigen", "SHOW_LESS": "Weniger anzeigen" }, "SKILLS": { "JAVA": "Java", "SPRING": "Spring Boot", "ANGULAR": "Angular", "VUE": "Vue.js", "REACT": "React", "DOCKER": "Docker", "UNITY": "Unity", "PYTHON": "Python", "CSHARP": "C#", "TYPESCRIPT": "TypeScript", "ARCH_MICROSERVICES": "Microservices", "ARCH_CLOUD": "Cloud Architecture", "ENG_ALGO": "Algorithm Design", "ENG_SIM": "3D Simulation", "ENG_GPU": "WebGPU / OpenGL / GLSL", "ENG_PERF": "Performance Optimization", "ENG_3D": "3D-Scanner Tech", "AI_AGENTS": "Claude Code / Agent SDK", "AI_MCP": "MCP-Server", "AI_CONTEXT": "Context- & Prompt-Engineering", "AI_HARNESS": "Agent-Harness-Design", "AI_GATES": "Automatisierte Review-Gates" }, "TOOLS": { "GIT": "Git", "GITHUB": "Github", "GITLAB": "Gitlab", "JENKINS": "Jenkins", "K8S": "Kubernetes / k3d", "POSTGRES": "PostgreSQL", "MONGO": "MongoDB", "GRAFANA": "Grafana/Prometheus", "DOCKER": "Docker" }, "XP": { "COMPANY9": { "COMPANY": "ColorDigital GmbH", "ROLE": "Senior Software Engineer - DMIxOS Team", "TIME": "März 2026 – heute", "HIGHLIGHTS": { "P1": "Konzeption und Implementierung der zentralen Cloud-Architektur.", "P2": "Full-Stack-Entwicklung und Optimierung neuer sowie bestehender Systemkomponenten.", "P3": "Technische Integration von Partnerunternehmen an das eigene Ökosystem." } }, "COMPANY8": { "COMPANY": "Teraport GmbH", "ROLE": "Senior Software Developer / Architect", "TIME": "Feb. 2024 – Feb. 2026", "HIGHLIGHTS": { "P1": "Architektur und Implementierung der Datenbankanbindung mit Hibernate 6.x.", "P2": "Konzeption und Entwicklung einer Full-Stack-Webanwendung für Kollisionsanalysen (Angular + Spring Boot + Docker).", "P3": "Entwicklung von MIDO, einem Tool zur Kostenkalkulation von Spritzguss-, Druckguss- und Stanzwerkzeugen: 3D-Layout von Werkzeugnest und Kavitäten, Bestimmung der Entformungsrichtung, Hinterschnitt-Erkennung und Kostenaufschlüsselung nach Werkzeugelementen." } }, "COMPANY7": { "COMPANY": "ColorDigital GmbH", "ROLE": "Lead Software Developer", "TIME": "März 2023 – Dez. 2023", "HIGHLIGHTS": { "P1": "Planung einer neuen Cloud-Architektur zur Migration eines umfangreichen Legacy-Monolithen in eine moderne Microservice-Plattform.", "P2": "Leitung eines Teams von drei bis vier Backend-Entwickler:innen.", "P3": "Zusammenarbeit mit Partnern und Kund:innen bei der Integration ihrer Systeme in die DMIx-Cloud." } }, "COMPANY6": { "COMPANY": "ColorDigital GmbH", "ROLE": "Senior Software Developer", "TIME": "März 2021 – März 2023", "HIGHLIGHTS": { "P1": "Arbeit an der DMIx-Core-Cloud, darunter die Implementierung einer Elasticsearch-basierten Echtzeit-Suchfunktion.", "P2": "Implementierung des neuen Browser-Tools „Moodboard“ (Node.js + Vue.js).", "P3": "Entwicklung von „PAX“, einem Tool für den Datenaustausch zwischen der DMIx-Cloud und PLM/ERP-Systemen, inkl. CI/CD-Integration." } }, "COMPANY5": { "COMPANY": "Assyst GmbH", "ROLE": "Teamleitung", "TIME": "Sep. 2015 – Feb. 2021", "HIGHLIGHTS": { "P1": "Teamleitung der Vidya-Softwareabteilung, inkl. Personalverantwortung und Bewerbungsprozesse.", "P2": "Leitung mehrerer Projekte mit externen Partnern und Freelancer:innen.", "P3": "Steuerung strategischer Initiativen im Bereich 3D-Softwareentwicklung." } }, "COMPANY4": { "COMPANY": "Assyst GmbH", "ROLE": "Software Engineer", "TIME": "Apr. 2010 – Feb. 2021", "HIGHLIGHTS": { "P1": "Arbeit an Kerntechnologien wie Algorithmen, Simulation, Rendering und Kollisions­erkennung.", "P2": "Einsatz verschiedener Technologien, u. a. Java, OpenCL, OpenGL, GLSL-Shader, C++ und REST-Schnittstellen.", "P3": "Mitarbeit an nationalen und internationalen Forschungsprojekten (EU7 und ZIM)." } }, "COMPANY3": { "COMPANY": "Assyst GmbH", "ROLE": "Praktikum und Diplomarbeit", "TIME": "Apr. 2009 – Apr. 2010", "HIGHLIGHTS": { "P1": "Implementierung produktiver Funktionen in der Echtzeit-Kleidersimulationssoftware Vidya.", "P2": "Durchführung von Recherchen und Literaturstudien für die Diplomarbeit.", "P3": "Erfolgreiche Umsetzung des Diplomthemas in Vidya." } }, "COMPANY2": { "COMPANY": "TH Bingen (University of Applied Sciences)", "ROLE": "Tutor für Grundlagen der Java-Programmierung", "TIME": "Apr. 2008 – Aug. 2008", "HIGHLIGHTS": { "P1": "Erstellung praktischer und theoretischer Lehrmaterialien.", "P2": "Durchführung einer 20-stündigen Vorlesungsreihe für den Bioinformatik-Studiengang.", "P3": "Unterstützung bei praktischen Programmierübungen." } }, "COMPANY1": { "COMPANY": "TH Bingen (University of Applied Sciences)", "ROLE": "Wissenschaftliche Hilfskraft", "TIME": "Okt. 2007 – Apr. 2008", "HIGHLIGHTS": { "P1": "Evaluation verschiedener Web-2.0-Technologien.", "P2": "Implementierung einer Java-basierten Schnittstelle zu Microsoft Excel.", "P3": "Integration des MIT-Exhibit-Frameworks und Vorbereitung für die CeBIT 2008." } }, "COMPANY0": { "COMPANY": "Chamaeleon AG", "ROLE": "Ausbildung Fachinformatiker Anwendungsentwicklung", "TIME": "Jul. 2002 – Jun. 2005", "HIGHLIGHTS": { "P1": "Entwicklung in PERL, PHP und ASP.", "P2": "Portierung, Wartung und Reengineering von bestehender Software.", "P3": "Regelmäßige Durchführung von Systemtests und Qualitätskontrollen, sowie deren Dokumentation." } } }, "PROJECT": { "TBP": { "TITLE": "To Be Played", "DESCRIPTION": "Ein Backlog-Tracker für Steam und PlayStation, der meine Bibliotheken synchronisiert, Status, Bewertung und Notizen verwaltet und passend zur verfügbaren Zeit vorschlägt, was ich als Nächstes spielen kann. Drei selbst gehostete Dienste: Auth, Core und ein React-Frontend.", "LINK_EXTERNAL": "tbp.andreas-dahm.eu", "LINK_INTERNAL": "Projektdetails", "HIGHLIGHTS": { "P1": "Steam- und PlayStation-Sync von Spielen, Spielzeit, Erfolgen und Trophäen, dazu die Spieldauer (Time to Beat) aus IGDB.", "P2": "„Was spiele ich als Nächstes?“: sieben Reihen mit Vorschlägen, gefiltert nach Zeitbudget; ein Stimmungsfilter für die Zufallsvorschläge.", "P3": "Auth signiert die Tokens mit Ed25519; Core holt den öffentlichen Schlüssel über die Key-ID und kann Tokens nur prüfen." } }, "CLAUDE_BOX": { "TITLE": "Claude-in-a-Box", "DESCRIPTION": "Eine Entwicklungsumgebung im Container, in der KI-Agenten Code nach festen Regeln schreiben. Eine Pipeline aus kleinen Agenten klärt, plant, implementiert, vereinfacht und reviewt, und ich sage Ja zum Plan und zu jedem Commit. Hooks außerhalb der Agenten blockieren rote Builds und Zugriffe auf Secrets.", "LINK_INTERNAL": "Projektdetails", "HIGHLIGHTS": { "P1": "Ein Agent pro Aufgabe, jeder mit frischem Kontext und passendem Modell. Die Übergabe läuft über Dateien, nicht über den Chat.", "P2": "Ein Commit-Hook führt Build, Tests und einen Secret-Scan aus und blockiert den Commit, wenn etwas rot ist.", "P3": "Eine Eval-Suite misst jede Änderung an Regeln oder Agenten an festen Aufgaben mit versteckten Prüfungen." } }, "P2": { "TITLE": "Playground", "DESCRIPTION": "Diese Seite. Ein Portfolio und ein Ort, um Algorithmen im Browser auszuprobieren: Pfadfindung, Sortieren, Fraktale in GLSL sowie eine Stoff- und eine Doppelpendel-Simulation als WebGPU-Compute-Shader. Angular, Deutsch und Englisch, gebaut und ausgeliefert von meiner eigenen CI.", "LINK_EXTERNAL": "andreas-dahm.eu", "LINK_INTERNAL": "Projektdetails", "HIGHLIGHTS": { "P1": "Visualisierungen von Dijkstra bis zu 3D-Fraktalen per Raymarching, alles im Client.", "P2": "Zwei WebGPU-Compute-Simulationen (Stoff mit XPBD, Doppelpendel) mit CPU- oder WebGL-Fallback.", "P3": "Pipeline auf Gitea Actions: Lint, i18n-Prüfung, Tests, Lighthouse, Docker-Image in meine eigene Registry." } }, "P1": { "TITLE": "El Mucho", "DESCRIPTION": "Mein erstes veröffentlichtes Spiel, ein rundenbasiertes taktisches Rollenspiel, das ich mit meinem Bruder in etwa drei Jahren gemacht und 2022 auf Steam veröffentlicht habe. Er hat es entworfen, ich habe es gebaut.", "LINK_EXTERNAL": "Steam Store", "LINK_INTERNAL": "Projektdetails", "HIGHLIGHTS": { "P1": "Der gesamte Code in Unity (C#), die Builds, das Steam-Setup und der Release.", "P2": "16 Missionen, Erfolge, drei Sprachen (EN, DE, ES).", "P3": "Eine eigene KI für die gegnerischen Einheiten und mein eigenes A*-Pathfinding auf dem Kartenraster." } }, "P0": { "TITLE": "Game Jams", "DESCRIPTION": "Drei 48-Stunden-Jam-Spiele aus der BeansJam-Reihe, in Teams aus zwei oder drei Leuten. Ich war jedes Mal Programmierer und Leiter. Alle sind auf itch.io spielbar.", "LINK_EXTERNAL": "Itch.io Sammlung", "LINK_INTERNAL": "Projektdetails", "HIGHLIGHTS": { "P1": "Peter Blue and the carnival of death (2018), Arkonauts (2020), Sloth Bros (2022).", "P2": "Freitag bis Sonntag: jedes Mal ein spielbares Spiel in 48 Stunden.", "P3": "Ein günstiger Weg, herauszufinden, ob eine Spielidee funktioniert, bevor man Jahre in sie steckt." } }, "DIPLOMA": { "TITLE": "Diplomarbeit - Kollisionserkennung und Behandlung von komplexen Kleidungsstücken.", "DESCRIPTION": "Selbstdurchdringungen innerhalb eines Kleidungsteils, etwa bei einem Plisseerock, in der Stoffsimulation Vidya bei assyst. Ich habe Intersection Contour Minimization an Vidyas k-DOP-Hierarchie angepasst und die Kollisionsantwort um Masse und Geschwindigkeit erweitert. FH Bingen, März 2010.", "LINK_INTERNAL": "Projektdetails", "HIGHLIGHTS": { "P1": "Erkennung auf der vorhandenen BVH mit k-DOPs; eine gemeinsame Hierarchie hat die Zeit für Update und Traversierung halbiert.", "P2": "Vier veröffentlichte Methoden verglichen und die globale Variante der Intersection Contour Minimization gewählt.", "P3": "Etwa 20 % schneller als der vorhandene Selbstkollisions-Detektor plus Layering, für den produktiven Einsatz vorgesehen." } }, "TRIBBLE": { "TITLE": "Homeserver 'Tribble'", "DESCRIPTION": "Ein Mini-PC zu Hause betreibt Gitea mit Actions-Runner und Registry, dazu Jellyfin, Paperless-ngx und AdGuard Home. Ein VPS liefert die öffentlichen Seiten aus und holt neue Images mit Watchtower über Tailscale. Läuft seit Mai 2024.", "LINK_INTERNAL": "Projektdetails", "HIGHLIGHTS": { "P1": "Gitea-CI baut und testet diese Seite und To Be Played zu Hause; Watchtower spielt die Images auf dem VPS ein.", "P2": "Traefik für TLS und Routing, Tailscale zwischen den beiden Maschinen.", "P3": "Schwierig waren DNS und ein wiederholbares Setup, nicht die Dienste selbst." } } }, "EDUCATION": { "E6": { "WHERE": "FH Bingen (University of Applied Sciences)", "WHEN": "2006 - 2010", "WHAT": "Diplom (FH), Angewandte Informatik (Schnitt 1.4)" }, "E5": { "WHERE": "FH Koblenz (University of Applied Sciences)", "WHEN": "2005 - 2006", "WHAT": "1 Semester Ingenieurinformatik" }, "E4": { "WHERE": "BBS Montabaur", "WHEN": "2002 - 2005", "WHAT": "Ausbildung zum Fachinformatiker Anwendungsentwicklung" }, "E3": { "WHERE": "BBS Andernach - Höhere Berufsbachschule (Informatik)", "WHEN": "2000 - 2002", "WHAT": "Fachhochschulreife" }, "E2": { "WHERE": "BBS Andernach - Berufsfachschule Technik (E-Technik)", "WHEN": "1998 - 2000", "WHAT": "Realschulabschluss" }, "E1": { "WHERE": "Hauptschule Hinter Burg Mayen (Albert Schweitzer Realschule plus)", "WHEN": "1995 - 1998 ", "WHAT": "Hauptschulabschluss" }, "E0": { "WHERE": "Albert Schweitzer Realschule Mayen", "WHEN": "1993 - 1995", "WHAT": "Kein Abschluss. Wechsel zur Hauptschule" } } }, "PROJECTS": { "DOWNLOAD": "Herunterladen", "READ_MORE": "Mehr erfahren", "DIAGRAM_ALT": "Architekturdiagramm", "LINK_TO_PROJECT": "Zum Projekt", "CLOSE": "Schließen", "SECTION": { "TECHNICAL": "Technische Herausforderungen", "LEARNINGS": "Learnings & Soft Skills", "DECISIONS": "Entscheidungen", "RESULT": "Ergebnis" }, "PLAYGROUND": { "TITLE": "Playground", "SHORT_DESCRIPTION": "Diese Seite: ein Portfolio mit Algorithmus-Visualisierungen, die im Browser laufen, einige davon auf der GPU.", "INTRODUCTION": "Auf dieser Seite probiere ich Dinge aus. Ich wollte sehen, wie weit ein normaler Browser mit den Algorithmen kommt, die ich mag. Deshalb läuft jede Visualisierung im Client: Pfadfindung, Labyrinth-Erzeugung, Sortieren, Conways Game of Life, Fraktale in GLSL-Shadern und zwei Physiksimulationen, die als Compute Shader auf WebGPU laufen. Der Rest ist eine normale Angular-App mit deutschen und englischen Texten, einem Dark Mode und einer Pipeline, die jeden Push auf main bis auf den Live-Server bringt.", "BULLET_1": "Pfadfindung mit Dijkstra und A*, Labyrinth-Erzeugung mit Prim und Kruskal, fünf Sortieralgorithmen mit Ton, Conways Game of Life und eine Vier-Farben-Karte.", "BULLET_2": "2D-Fraktale (Mandelbrot, Julia, Burning Ship, Newton) in einem GLSL-Shader und 3D-Fraktale (Mandelbulb, Mandelbox, Julia-Bulb) per Raymarching, mit Normalen aus dem Distanzfeld.", "BULLET_3": "Ein Doppelpendel und eine Stoffsimulation laufen als Compute Shader auf WebGPU. Ohne WebGPU weichen sie auf die CPU oder WebGL aus und melden das in einer Snackbar.", "BULLET_4": "Angular mit Material und Tailwind, Signals für den Zustand, Deutsch und Englisch. Jede Route wird beim Build in eine eigene HTML-Datei mit eigenem Titel und eigenen Meta-Tags vorgerendert, damit ein geteilter Link eine richtige Vorschau bekommt. Im Browser übernehmen dann Angular und ein Service Worker (PWA) die statische Seite.", "BULLET_5": "Privacy by Design: Die Schriften kommen von der Seite selbst, es gibt keine Cookies, kein Tracking und kein Access-Log auf dem Server. Nur Sprache und Theme liegen im Local Storage des Browsers.", "BULLET_6": "Betrieb: Die CI auf Gitea Actions auf meinem Homeserver Tribble führt Lint, i18n-Prüfung, Tests, den Produktions-Build, eine Prerender-Prüfung und Lighthouse aus. Ein Push auf main baut ein Docker-Image; auf dem VPS holt Watchtower es ab, nginx liefert die statischen Dateien aus und Traefik übernimmt HTTPS. Gitea selbst ist über Traefik und Tailscale erreichbar.", "CHALLENGE_1": "Der Stoff-Solver auf der GPU. XPBD-Constraints, die sich einen Partikel teilen, lassen sich nicht parallel lösen, ohne eine Race Condition auszulösen. Ich gruppiere die Constraints per Graphfärbung in acht Phasen. So berühren innerhalb einer Phase nie zwei Constraints denselben Partikel, und jede Phase läuft voll parallel.", "CHALLENGE_2": "Nicht jeder Browser hat WebGPU. Beide Simulationen haben einen CPU- oder WebGL-Pfad und eine Snackbar, die sagt, dass der Fallback aktiv ist. So funktioniert die Seite überall, und niemand fragt sich, warum es langsamer ist.", "CHALLENGE_3": "Eine bestehende SPA vorrendern. Beim Build wird jede Seite in Node gerendert, und dort gibt es kein WebGPU, kein Canvas für Babylon.js und keinen localStorage. Browser-Code musste Komponente für Komponente abgesichert werden, bis der Build durchlief. Die Client-Shell index.csr.html bleibt, weil Service Worker und 404-Seite sie weiter brauchen.", "LEARNING_1": "Compute Shader verändern, wie man über einen Algorithmus denkt. Das Datenlayout und die Reihenfolge der Schreibzugriffe zählen mehr als die Mathematik.", "LEARNING_2": "Eine kleine CI mit einem Abgleich der Übersetzungsschlüssel und einem Lighthouse-Lauf fängt die langweiligen Fehler ab (eine fehlende Übersetzung, eine langsame Seite), bevor ich sie auf der Live-Seite sehe.", "LEARNING_3": "Eine Datenschutzseite ist eine Vorgabe für den Server. Sobald dort steht, dass es kein Access-Log und keine Verbindung zu Google gibt, darf nginx kein Log schreiben, und die Schriften müssen mit der Seite ausgeliefert werden. Es zuerst aufzuschreiben hat mir diese Entscheidungen abgenommen.", "DECISION_1": "Vorrendern beim Build statt Client-Rendering oder einem SSR-Server. Jede Route wird beim Build zu einer eigenen HTML-Datei, die nginx als statische Datei ausliefert; auf dem VPS läuft kein Node-Prozess. Der Grund war die Link-Vorschau: Ein geteilter Link braucht Titel und Meta-Tags im HTML, nicht erst, nachdem die App geladen hat. Ein Node-Server wäre für eine Seite, deren Inhalt ohnehin im Browser läuft, zu aufwendig gewesen.", "DECISION_2": "Vitest mit jsdom statt Karma mit Chrome. Karma brauchte einen Headless-Chrome im CI-Container; Vitest läuft ohne Browser. Der Nachteil: jsdom wendet kein echtes CSS an, zwei Tests auf die geladenen Schriften sind dabei gestrichen worden.", "DECISION_3": "Tailwind ohne Preflight statt SCSS-first oder Tailwind komplett. Die Styles sind aus einer großen styles.scss in Utility-Klassen im Template umgezogen, damit ein Style neben dem Markup steht, das er betrifft; SCSS bleibt nur für das Material-Theme und seine Overrides. Tailwinds eigener Reset (Preflight) bleibt draußen, damit der Reset von Angular Material Vorrang behält.", "RESULT": "Die Seite läuft so, wie die Datenschutzseite es verspricht: ohne Cookies, ohne Tracking, ohne Google-Fonts-Request, und der Build schlägt fehl, sobald einer auftaucht. Ein Push auf main ist nach wenigen Minuten live, ohne manuellen Schritt. Geteilte Links zeigen jetzt Titel und Vorschau, und die CI fängt fehlende Übersetzungen und einen fehlenden Titel oder Canonical-Tag ab, bevor ich sie auf der Live-Seite sehe." }, "TRIBBLE": { "TITLE": "Self-Hosted Infrastructure", "SHORT_DESCRIPTION": "Home-Server für CI und private Dienste, dazu ein VPS für die öffentlichen Seiten.", "INTRODUCTION": "Zwei Maschinen. Zu Hause läuft auf einem Mini-PC namens „Tribble“ Gitea mit Actions-Runner und Container-Registry, dazu die privaten Dienste. Ein Hostinger-VPS liefert die öffentlichen Seiten aus, auch diese hier. Ein Git-Push baut das Image zu Hause, der VPS holt es mit Watchtower über Tailscale, und Traefik übernimmt TLS und Routing. Das Ganze läuft seit Mai 2024.", "BULLET_1": "Hardware: ein KODLIX GD50 Mini-PC mit einem 8-Kern-Intel-Core-i5, 32 GB RAM und einer 512-GB-SSD.", "BULLET_2": "Gitea für Git, den Actions-Runner und die Container-Registry. Diese Seite und To Be Played werden dort gebaut und getestet.", "BULLET_3": "Private Dienste: Jellyfin für Medien, Paperless-ngx für Dokumente, AdGuard Home als DNS-Server und Werbeblocker, Traefik davor.", "BULLET_4": "Tailscale verbindet die beiden Maschinen. Darüber holt der VPS neue Images aus der Registry zu Hause, und Watchtower tauscht den laufenden Container aus.", "CHALLENGE_1": "DNS. Namen müssen von zu Hause aus, über Tailscale und aus dem öffentlichen Internet auf die richtige Adresse zeigen, und AdGuard Home sitzt mittendrin. Das richtig hinzubekommen hat länger gedauert als jeder einzelne Dienst.", "CHALLENGE_2": "Alles gleichzeitig zum Laufen zu bringen und es dann wiederholbar zu machen. Das erste Setup war eine lange Liste von Handgriffen. Ich habe es automatisiert, damit sich der Server neu aufsetzen lässt, ohne dass ich mir jeden Schritt merken muss.", "LEARNING_1": "Selbst hosten kostet wenig Geld und viel Zeit. Die meiste Zeit geht in die Teile, die niemand sieht: DNS, Zertifikate und Updates.", "LEARNING_2": "Ein Git-Push, der ohne Handgriff dazwischen in einem laufenden Container auf dem VPS endet, ist der Teil dieses Setups, den ich nicht mehr hergeben würde.", "DECISION_1": "Der VPS ist der einzige öffentliche Eingang. Traefik auf dem VPS leitet die Namen, die zu Hause gehostet werden, über Tailscale an den Heimserver weiter, mit ein paar YAML-Dateien im File-Provider. Die Alternative wäre eine Portweiterleitung im Router gewesen. So bleibt zu Hause kein Port offen, und die Adresse des Heimservers bleibt privat.", "DECISION_2": "Auf dem VPS zieht Watchtower, zu Hause bleiben die Tags gepinnt. Der VPS schaut jede Minute nach neuen Images und tauscht nur die markierten Container aus, einen nach dem anderen; die CI braucht dafür keinen Zugang zum VPS. Der Heimserver bekommt kein „latest“: Dort ist jedes Image-Tag festgeschrieben, und ein Update ist ein manueller Schritt pro Stack, weil mit „latest“ eine Datenbankmigration als Nebenwirkung passiert, und die lässt sich nicht zurücknehmen.", "DECISION_3": "Neu aufsetzen per Skript statt aus dem Gedächtnis. bootstrap.sh legt das Proxy-Netz und die Verzeichnisse an und warnt vor dem DNS-Port-Konflikt zwischen systemd-resolved und AdGuard; rebuild.sh startet die Stacks in fester Reihenfolge, Traefik zuerst, weil es alphabetisch zuletzt käme. Ansible hätte bei sechs Compose-Stacks mehr Aufwand gekostet, als es spart. Zwei Shell-Skripte reichen und sind leichter zu lesen. Die Skripte decken die Infrastruktur ab, nicht die Daten.", "RESULT": "Heute laufen zu Hause sechs Stacks: Traefik, AdGuard Home, Gitea mit Runner, Jellyfin, Paperless-ngx und Leantime; Nextcloud ist wieder abgeschaltet. Auf dem VPS laufen Traefik, Watchtower und die öffentlichen Seiten. Jeder Push dieser Seite und von To Be Played wird zu Hause gebaut, in die Registry zu Hause geschoben und kommt ohne manuellen Schritt auf den VPS. Im September habe ich alle Stacks in einem Durchgang aktualisiert, Stack für Stack mit den gepinnten Tags, ohne dass ein Dienst ausgefallen ist." }, "EL_MUCHO": { "TITLE": "El Mucho (Steam Release)", "SHORT_DESCRIPTION": "Ein rundenbasiertes taktisches Rollenspiel, das mein Bruder und ich auf Steam veröffentlicht haben.", "INTRODUCTION": "El Mucho ist ein rundenbasiertes taktisches Rollenspiel in einer erfundenen Welt namens Liberika, inspiriert von Langrisser. Mein Bruder und ich haben es in etwa drei Jahren neben einem Vollzeitjob gebaut und auf Steam veröffentlicht: Early Access im Juli 2021, die Vollversion im Februar 2022. Die Aufteilung war klar. Er hatte die Idee und hat die Assets, das Leveldesign, das Testen und das Balancing gemacht. Ich habe alles Technische gemacht: den Code in Unity, die Builds, das Steam-Setup und den Release.", "BULLET_1": "Unity und C#. Ein Entwickler für den gesamten Code.", "BULLET_2": "16 Missionen, Erfolge und Familienfreigabe auf Steam, veröffentlicht als Lobo Corp.", "BULLET_3": "Englisch, Deutsch und Spanisch. Die spanische Übersetzung hat ein Freund gemacht, der Muttersprachler ist.", "BULLET_4": "Grafik und Musik sind teils Open Source, teils von meinem Bruder, teils von mir.", "CHALLENGE_1": "Der Steam-Release als Ein-Personen-Technikteam. Builds, Store-Einrichtung, Upload und die Early-Access-Phase liefen alle über mich. Early Access startete im Juli 2021, die Vollversion folgte im Februar 2022.", "CHALLENGE_2": "Drei Sprachen. Jeden Text gibt es auf Englisch, Deutsch und Spanisch; für Spanisch habe ich mit einem Freund zusammengearbeitet, der Muttersprachler ist.", "CHALLENGE_3": "Die gegnerischen Einheiten. Dafür habe ich eine eigene KI und mein eigenes A*-Pathfinding auf dem Kartenraster geschrieben.", "LEARNING_1": "Fertigstellen dauert länger als Bauen. Allein die Strecke vom Early Access bis zur Vollversion hat etwa sieben Monate gedauert.", "LEARNING_2": "Eine klare Rollenverteilung hat ein Zwei-Personen-Team drei Jahre lang arbeitsfähig gehalten: Er war für das Spiel zuständig, ich für den Code und den Release." }, "GAME_JAMS": { "TITLE": "Game Jams", "SHORT_DESCRIPTION": "Drei kleine Spiele, jedes in 48 Stunden von Freitag bis Sonntag entstanden.", "INTRODUCTION": "Bei einer Game Jam bekommt man am Freitagabend ein Thema und soll bis Sonntag ein spielbares Spiel abliefern. Ich habe bisher drei gemacht, alle in der BeansJam-Reihe, in Teams aus zwei oder drei Leuten. Ich war immer Programmierer und Leiter; die anderen haben Ideen, Assets und Tests beigesteuert. Alle drei Spiele gibt es auf itch.io.", "BULLET_1": "Peter Blue and the carnival of death (2018, BeansJam): meine erste Jam.", "BULLET_2": "Arkonauts (2020, Mini Beans Jam 6): Space Invaders im Weltraum.", "BULLET_3": "Sloth Bros (2022, BeansJam '22): Thema „wir müssen wach bleiben“, eines von 65 Spielen.", "BULLET_4": "Immer 48 Stunden, von Freitag bis Sonntag, in einem Team aus zwei oder drei Leuten.", "CHALLENGE_1": "Der Umfang. 48 Stunden sind nicht viel, und mit Schlaf sind es weniger. Am Freitag zu entscheiden, was wir nicht bauen, war in jeder Jam die wichtigste Entscheidung.", "CHALLENGE_2": "Als einziger Programmierer. Alles, was sich das Team ausgedacht hat, ging durch meine Tastatur. Eine Idee musste also billig zu bauen sein, sonst wurde sie gestrichen.", "LEARNING_1": "Ein spielbarer Gameplay-Loop am Samstag schlägt eine bessere Idee am Sonntag.", "LEARNING_2": "In einer Jam finde ich heraus, ob eine Spielidee funktioniert, bevor ich Jahre in sie stecke. El Mucho hat drei Jahre gedauert; ein Jam-Spiel dauert ein Wochenende.", "LINK_COLLECTION": "Alle Spiele auf itch.io" }, "DIPLOMA": { "TITLE": "Diplomarbeit: Selbstkollisionen bei Stoffen", "SHORT_DESCRIPTION": "Selbstdurchdringungen in simulierter Kleidung erkennen und auflösen, entstanden bei assyst für die Stoffsimulation Vidya (2010).", "INTRODUCTION": "Simulierter Stoff faltet sich in sich selbst hinein. Ein Plisseerock durchdringt zum Beispiel sein eigenes Gewebe, und sobald zwei Teile eines Kleidungsstücks einander durchdrungen haben, kann eine normale Kollisionsantwort sie nicht mehr voneinander trennen. Kollisionen zwischen verschiedenen Lagen konnte Vidya schon; Selbstdurchdringungen innerhalb eines Kleidungsteils nicht. In meiner Diplomarbeit bei assyst habe ich Intersection Contour Minimization (Volino et al. 2006) an Vidya angepasst und so erweitert, dass die Kollisionsantwort Masse und Geschwindigkeit berücksichtigt. Geschrieben in Java, im März 2010 an der FH Bingen abgeschlossen und für den produktiven Einsatz vorgesehen.", "BULLET_1": "Die Erkennung läuft auf Vidyas vorhandener Bounding-Volume-Hierarchie mit k-DOPs. Bei Selbsttests überspringt die Traversierung die Tests an inneren Knoten, die sie nicht braucht.", "BULLET_2": "Die Schnittkontur wird aus Segmenten aufgebaut. Ein räumlicher Hash verbindet sie auch dann, wenn Gleitkommafehler kleine Lücken lassen.", "BULLET_3": "Die Kollisionsantwort ist meine Erweiterung: Sie nutzt Masse und Geschwindigkeit, sodass das Ergebnis physikalisch plausibel ist und nicht nur ein geometrischer Schubs.", "BULLET_4": "Der Parameter h0 wird aus der mittleren Dreiecksfläche abgeleitet und muss daher nicht von Hand eingestellt werden.", "BULLET_5": "Die Methode teilt sich eine BVH mit Vidyas vorhandenem TriangleSelfCollisionDetector.", "CHALLENGE_1": "Die Kosten. Die Testszene hatte etwa 16.000 Partikel bei etwa 430 ms pro Schritt auf einem 2-GHz-Core-2-Duo. Eine gemeinsame BVH für meinen Detektor und den vorhandenen hat die Zeit für Update und Traversierung halbiert und den Overhead um etwa 80 % gesenkt. Zusätzlich zur vorhandenen Erkennung kostet die Methode etwa 5 % mehr.", "CHALLENGE_2": "Die Wahl der Methode. Ich habe Baraff (2003), Bridson (2002) und Volino (2000, 2006) verglichen und die globale Variante der Intersection Contour Minimization genommen, weil sie auch Durchdringungen auflöst, die schon bestehen.", "CHALLENGE_3": "Bekannte Grenzen, dokumentiert: nahezu ebene Flächen und ein Vertex, der genau auf einer Dreiecksebene liegt, werden nicht behandelt.", "LEARNING_1": "Paper richtig lesen. Das gleiche Problem hatte vier veröffentlichte Lösungen, und die nützliche Arbeit war zu verstehen, wo jede davon scheitert.", "LEARNING_2": "Erst messen, dann behaupten. Die Methode allein war etwa 20 % schneller als der vorhandene Detektor plus Layering, und diese Zahl zählt nur, weil sie aus derselben Szene auf derselben Maschine stammt." }, "CLAUDE_BOX": { "TITLE": "Claude-in-a-Box", "SHORT_DESCRIPTION": "Ein Container-Setup für einen KI-Coding-Agenten: eine Pipeline aus kleinen Agenten mit je einer Aufgabe und Hooks, an denen keiner von ihnen vorbeikommt.", "INTRODUCTION": "Ein KI-Coding-Agent produziert den ganzen Tag Code, ohne sich zu beschweren. Das Problem ist, dass das Ergebnis meistens vernünftig aussieht, auch wenn es falsch ist, und die ganzen Kosten damit im Review landen. Also habe ich ein Docker-Setup gebaut, das den Agenten denselben Regeln unterwirft wie alle anderen, die am Repository arbeiten: Er liest die Architekturregeln des Projekts, in dem er gerade steckt, und der Build muss durchlaufen, bevor ein Commit möglich ist. Inzwischen ist aus der Box eine Pipeline geworden. Das Klären passiert im Chat, danach gibt es einen kleinen Agenten pro Aufgabe, jeder mit frischem Kontext: Einer plant, einer implementiert, einer vereinfacht, einer reviewt. Die Übergabe läuft über Dateien, nicht über den Chat, und ich sage an zwei Stellen Ja: zum Plan und zu jedem Commit. Die Hooks um das Ganze herum sind immer noch der Teil, auf den ich am häufigsten zurückkomme, weil sie das Einzige sind, woran sich kein Agent vorbeireden kann.", "BULLET_1": "Agenten-Pipeline: Klären (die Hauptsession stellt gezielte Fragen und schreibt eine Spec), Planen, Implementieren, Vereinfachen, ein Security-Check, wenn die Änderung etwas Sicherheitsrelevantes berührt, Review, Commit-Message. Jeder Schritt nach dem Klären ist ein eigener Agent mit frischem Kontext und einem Modell, das zur Aufgabe passt: Fable für Klären, Planen und Review, Sonnet fürs Implementieren, Opus für Vereinfachen und Security, Haiku für die Commit-Message. Sind weniger als 20 % des wöchentlichen Fable-Budgets übrig, wechseln diese Schritte von selbst auf Opus, und die Statusleiste zeigt es an.", "BULLET_2": "Ich bleibe an zwei Stellen im Spiel: Der Plan braucht mein OK, bevor jemand Code schreibt, und jeder Commit braucht mein Ja. Der Reviewer ist kritisch und kennt den Chat nicht: Er lässt das Gate laufen, schreibt für jeden Fund einen fehlschlagenden Test und prüft, ob das Feature funktioniert, wenn nötig im Browser. Funde gehen zurück an den Implementierer, höchstens zwei Runden. Ein Agent, der denselben Fehler zweimal nicht beheben kann, hört auf und meldet RED, und ich entscheide. Kleine Änderungen überspringen Plan und Review.", "BULLET_3": "Die Regeln werden per Erkennung geladen. Ein Repo mit pom.xml bekommt die Java- und Spring-Regeln, eines mit package.json die TypeScript-Regeln, ein Unity-Projekt die datenorientierten Regeln, und Repos mit eigenen Architekturnotizen bekommen diese zusätzlich. Jeder Agent liest nur die Dokumente, die seine Aufgabe braucht, damit der Kontext klein bleibt.", "BULLET_4": "Hooks außerhalb der Agenten: ein Compile- oder Type-Check am Ende jedes Turns, ein Commit-Gate, das den vollen Build, die Tests und einen Secret-Scan ausführt und einen roten Commit verweigert, ein Guard, der das Lesen von .env-Dateien und Keys blockiert, und ein Work-Mode-Guard. Drumherum ein Container ohne Root mit festen Toolchains und MCP-Servern für Code-Graph-Abfragen, Gitea und lesendes Jira.", "BULLET_5": "Work-Mode assist, pro Chat: Ich schreibe den Kern (Domain, Services, Persistenz), die Agenten schreiben Gerüst, Boilerplate und Tests und reviewen meinen Code. Ein Hook verweigert den Agenten das Schreiben auf die Kernpfade, die Trennung wird also erzwungen und nicht nur erbeten.", "BULLET_6": "Eine Eval-Suite lässt die ganze Pipeline headless gegen feste Aufgaben laufen, Fallen eingeschlossen, jede mit versteckten Prüfungen. Die Ergebnisse werden pro Commit der Box protokolliert, eine Änderung an einer Regel oder einem Agenten wird also gemessen statt geraten.", "CHALLENGE_1": "Die Regeln kurz genug zu halten, dass sie noch etwas bringen. Lange allgemeine Richtlinien werden einfach ignoriert, also sind sie in Dateien pro Stack, pro Repo und pro Aufgabe aufgeteilt, und jeder Agent lädt nur die, die für ihn gelten.", "CHALLENGE_2": "Die Gates so zu bauen, dass kein Agent sich daran vorbeireden kann. Jede Prüfung läuft als Hook außerhalb des Agenten: Ein fehlgeschlagener Build blockiert den Commit, ein Lesezugriff auf Secrets wird verweigert, ein Schreibzugriff auf einen Kernpfad im Assist-Modus abgelehnt, unabhängig davon, was der Agent über seine eigene Arbeit denkt.", "CHALLENGE_3": "Ein Reviewer, der zugesehen hat, wie der Code entstanden ist, winkt ihn durch. Also startet jeder Agent mit leerem Kontext und bekommt nur die Dateien, die seine Aufgabe braucht: Der Reviewer sieht Spec, Plan und Diff, nie den Chat. Der Preis ist, dass jeder Agent den Code neu liest, die Pipeline ist also langsamer als ein langer Chat.", "CHALLENGE_4": "Zu wissen, ob eine Änderung an der Box etwas bringt. Jede angepasste Regel fühlte sich wie eine Verbesserung an, also habe ich die Eval-Suite gebaut: feste Aufgaben mit versteckten Prüfungen, vor und nach einer Änderung mit denselben Modellen ausgeführt. Die Suite prüft auch sich selbst: Die unveränderte Vorlage muss an jeder Aufgabe scheitern und die Referenzlösung muss bestehen, sonst sagt ein grüner Lauf nichts aus.", "LEARNING_1": "Die Formulierung der Prompts war deutlich weniger wichtig, als ich dachte. Fast der ganze Unterschied kam vom Drumherum.", "LEARNING_2": "Den Output des Agenten genauso zu reviewen und durch dieselben Gates zu schicken wie den Pull Request eines neuen Teammitglieds hat sich als die nützlichste Regel überhaupt herausgestellt.", "LEARNING_3": "Kleine Agenten mit jeweils frischem Kontext schlagen einen langen Chat. Nicht weil das Modell klüger wird, sondern weil niemand frühere Fehler mitschleppt und jede Übergabe eine Datei ist, die ich lesen und korrigieren kann.", "DECISION_1": "Hooks statt Berechtigungsregeln. Claude Code kann Werkzeuge per Regel sperren, aber eine Regel gilt pro Werkzeug: Ein Leseverbot für .env-Dateien hält „cat .env“ in der Shell nicht auf. Deshalb sind der Secret-Guard, das Commit-Gate und der Type-Check Hooks, die jeden Aufruf sehen. Und seit ein Timeout im Hook einmal Commits ungeprüft durchgelassen hat, schließt das Gate bei jedem Fehler: Keine Antwort heißt rot.", "DECISION_2": "Der Docker-Socket des Hosts statt eines eigenen Docker-Daemons im Container. Damit laufen die Testcontainers-Tests der Java-Repos im Commit-Gate, statt übersprungen zu werden. Der Nachteil steht im README: Wer den Socket hat, ist auf dem Host root. Die Box ist also eine Arbeitsumgebung mit festen Werkzeugen, keine Sandbox.", "DECISION_3": "Ein zweites Modell statt nur eines zweiten Kontexts. Anfangs lief alles auf einem Modell, das zweite wurde nie benutzt. Jetzt ist das Modell, das plant und reviewt, nicht das, das implementiert, weil eine zweite Meinung mehr wert ist, wenn sie nicht aus demselben Modell kommt. Für einen Ein-Datei-Fix lohnt der Wechsel nicht, deshalb überspringen kleine Änderungen einzelne Schritte der Pipeline.", "RESULT": "Die Box ist seit Juli 2026 mein tägliches Werkzeug, in jedem Repo, an dem ich arbeite, im Job und zu Hause. Der Build mit echten Testcontainers läuft im Commit-Gate. Die Pipeline besteht jede Aufgabe der Eval-Suite, während der unveränderte Ausgangszustand an jeder scheitert. Das zeigt vor allem, dass die Aufgaben noch zu leicht sind; härtere sind der nächste Schritt." }, "TBP": { "TITLE": "To Be Played", "SHORT_DESCRIPTION": "Ein Backlog-Tracker für Steam und PlayStation, der bei der Entscheidung hilft, was als Nächstes gespielt wird.", "INTRODUCTION": "Meine Spielebibliothek hat weit mehr Spiele, als ich je durchspielen werde. To Be Played synchronisiert Steam und PlayStation, importiert Listen von GOG, Playnite, Grouvee und HowLongToBeat, verwaltet den Backlog mit Status, Bewertung und Notizen und schlägt passend zur verfügbaren Zeit vor, was ich als Nächstes spielen kann. Dahinter stehen drei Dienste: ein Auth-Service, der die Login-Tokens signiert, ein Core-Service für Bibliothek und Statistiken und ein React-Frontend. Alles läuft selbst gehostet auf demselben Server wie diese Seite.", "BULLET_1": "Steam-Sync von Spielen, Spielzeit, Erfolgen und Wunschliste, PlayStation-Sync von Titeln, Spielzeit und Trophäen, von Hand und jede Nacht, dazu die Spieldauer (Time to Beat) aus IGDB.", "BULLET_2": "„Was spiele ich als Nächstes?“: sieben Reihen mit je drei Vorschlägen, etwa der nächste Teil einer Reihe, dasselbe Studio oder Spiele, die Leute gespielt oder durchgespielt haben, denen man folgt. Ein Zeitbudget filtert jede Reihe; der Stimmungsfilter wirkt nur auf die Zufallsvorschläge.", "BULLET_3": "Öffentliche Profile, anderen Spielern folgen mit einer Inbox zu ihrem Fortschritt und Pile Points für jedes Spiel, das vom Stapel kommt.", "BULLET_4": "Deutsch und Englisch, Light- und Dark-Mode, auch auf dem Handy nutzbar. Auth, Core und Frontend werden jeweils als eigenes Docker-Image ausgeliefert.", "BULLET_5": "Über 3.400 Tests in den drei Diensten, ArchUnit-Regeln, die die Schichten getrennt halten, und Testcontainers für die echte Datenbank. Die CI führt den vollständigen Build aus und taggt jedes Image mit dem Commit.", "CHALLENGE_1": "Login über zwei Dienste. Auth signiert die Tokens mit einem Ed25519-Schlüssel, Core kennt nur den öffentlichen Schlüssel und kann einen Login also prüfen, aber nie selbst ausstellen. Core holt diesen Schlüssel zur Laufzeit über die Key-ID von Auth, und die Tokens liegen in HttpOnly-Cookies, kein Skript auf der Seite kann sie lesen.", "CHALLENGE_2": "Die Limits von Steam. Jeder Steam-Aufruf ist auf zwei pro Sekunde begrenzt und wird nur dort wiederholt, wo eine Wiederholung sicher ist (Spielebibliothek, Schema, Erfolge). Ein Sync reserviert seinen Lauf zuerst in der Datenbank, damit nie zwei Syncs desselben Users gleichzeitig laufen, und der nächtliche Sync berührt nur User, die in den letzten 14 Tagen aktiv waren.", "CHALLENGE_3": "Ein Spiel vorzuschlagen ist schwerer, als es klingt. Jede Vorschlagsreihe braucht eigene Regeln, und die Listen hinter jeder Zahl müssen dieselben Regeln nutzen wie die Zahl selbst, damit ein Spiel überall gleich zählt.", "CHALLENGE_4": "Missbrauch beim Login. Zu viele fehlgeschlagene Logins bekommen die Antwort 429 mit Retry-After. Auch bei einer unbekannten E-Mail läuft eine volle BCrypt-Prüfung, sodass die Antwortzeit nichts verrät. Ein Header-Schutz blockiert CSRF, CORS erlaubt nur exakte Origins, und das PlayStation-Token wird verschlüsselt gespeichert.", "LEARNING_1": "Die Aufteilung in Dienste lohnt sich nur mit einem klaren Vertrag. Die Token-Claims stehen in einer Vertragsdatei, gegen die beide Dienste testen, und ein Skript, das ich von Hand starte, prüft, dass beide Kopien gleich sind.", "LEARNING_2": "Auch ein Hobbyprojekt braucht die langweiligen Teile: tägliche Datenbank-Dumps, die sieben Tage, vier Wochen und sechs Monate aufbewahrt werden, Migrationen und eine schriftlich festgehaltene Deploy-Reihenfolge. Sie haben mehr darüber entschieden, wie stabil es läuft, als jedes Feature.", "DECISION_1": "Ein Schlüsselpaar statt eines gemeinsamen Geheimnisses. Die erste Version hat die Tokens mit einem HMAC-Secret signiert, das Auth und Core beide kannten. Jetzt hält Auth den privaten Ed25519-Schlüssel und veröffentlicht den öffentlichen über einen JWKS-Endpunkt. Core kann einen Login prüfen, aber nie selbst ausstellen, und wenn ein Token eine unbekannte Key-ID trägt, holt Core sich den neuen Schlüssel selbst.", "DECISION_2": "Mails über eine Outbox-Tabelle statt direkt per SMTP. Jede Mail wird in der Transaktion ihres Ereignisses als Zeile in mail_outbox geschrieben, ein Job verschickt die Zeilen jede Minute. So scheitert eine Registrierung nie am Mailserver, und beim Neustart von Auth geht keine Mail verloren. Mit einer Auth-Instanz reicht „mindestens einmal“.", "DECISION_3": "Installierbar, aber ohne Service Worker. Die App hat ein Web-App-Manifest und lässt sich auf Handy und Desktop installieren, aber keinen Service Worker. So trifft ein Deploy nie auf ein altes, gecachtes Bundle. Der Nachteil ist ein fehlender Offline-Modus.", "RESULT": "Heute laufen Auth, Core und das Frontend als eigene Container neben dieser Seite, mit einer PostgreSQL-Instanz mit zwei Datenbanken, nächtlichem Sync und täglichen Dumps. Registrieren kann sich jeder mit bestätigter E-Mail, freigeschaltet wird von Hand. Ich nutze es täglich: Was ich als Nächstes spiele, kommt jetzt aus einer Liste, nicht mehr aus dem Scrollen durch die Steam-Bibliothek." } }, "IMPRINT": { "TITLE": "Impressum", "PARAGRAPH": "Angaben gemäß § 5 DDG", "COUNTRY": "Deutschland", "CONTACT": "Kontakt" }, "PRIVACY": { "TITLE": "Datenschutzerklärung", "CONTROLLER": "Verantwortlicher", "SECTIONS": { "HOSTING": { "TITLE": "Hosting", "TEXT": "Diese Seite läuft auf einem virtuellen Server (VPS) von Hostinger International Ltd., 61 Lordou Vironos str., 6023 Larnaca, Zypern. Mit Hostinger besteht ein Auftragsverarbeitungsvertrag nach Art. 28 DSGVO (Hostinger Data Processing Agreement). Der Webserver speichert keine Zugriffs-Logs. Nur bei technischen Fehlern kann ein Fehler-Log kurzzeitig die IP-Adresse enthalten. Rechtsgrundlage: Art. 6 Abs. 1 lit. f DSGVO (sicherer Betrieb der Seite)." }, "STORAGE": { "TITLE": "Lokaler Speicher", "TEXT": "Die Seite speichert nur Sprache und Design (hell oder dunkel) im localStorage des Browsers. Es gibt keine Cookies, kein Tracking und keine Analyse-Tools." }, "SERVICE_WORKER": { "TITLE": "Service Worker", "TEXT": "Ein Service Worker legt Dateien der Seite im Browser-Cache ab, damit die Seite schneller und offline lädt. Es werden keine Daten an Dritte gesendet." }, "FONTS": { "TITLE": "Schriften", "TEXT": "Schriften (Roboto, Material Icons) werden vom eigenen Server geladen. Es gibt keine Verbindung zu Google." }, "EXTERNAL_LINKS": { "TITLE": "Externe Links", "TEXT": "Links zu Gitea, LinkedIn und Codeberg werden erst beim Klick aufgerufen. Ab dann gilt die Datenschutzerklärung des jeweiligen Anbieters." }, "RIGHTS": { "TITLE": "Ihre Rechte", "TEXT": "Sie haben das Recht auf Auskunft, Berichtigung, Löschung, Einschränkung der Verarbeitung, Datenübertragbarkeit und Widerspruch (Art. 15 bis 21 DSGVO). Kontakt: die E-Mail-Adresse oben." }, "AUTHORITY": { "TITLE": "Beschwerderecht", "TEXT": "Sie können sich bei einer Aufsichtsbehörde beschweren. Zuständig: Bayerisches Landesamt für Datenschutzaufsicht (BayLDA), Promenade 18, 91522 Ansbach." } } }, "PATHFINDING": { "TITLE": "Pfadfindungsalgorithmen", "START_NODE": "Startknoten", "END_NODE": "Endknoten", "WALL": "Wand", "CLEAR_NODE": "Löschen", "DIJKSTRA": "Start Dijkstra", "ASTAR": "Start A*", "NORMAL_CASE": "Testaufbau", "EDGE_CASE": "A* Grenzfall-Aufbau", "RANDOM_CASE": "Zufälliger-Aufbau", "CLEAR_BOARD": "Leeres Gitter", "VISITED": "Besucht", "PATH": "Pfad", "COMPARE": "Vergleich (A* vs Dijkstra)", "VISITED_NODES": "Besuchte Knoten", "PATH_LENGTH": "Pfadlänge", "EXECUTION_TIME": "Ausführungszeit", "EXPLANATION": { "TITLE": "Algorithmen", "DIJKSTRA_EXPLANATION": " findet garantiert den kürzesten Weg, wenn alle Kantenkosten nicht-negativ sind. Vorteil: optimal und ohne Heuristik. Nachteil: besucht oft sehr viele Knoten (kann bei großen Grids langsamer wirken).", "ASTAR_EXPLANATION": " erweitert Dijkstra um eine Heuristik (z.B. Manhattan-Distanz) und kann dadurch wesentlich zielgerichteter suchen. Vorteil: oft deutlich schneller bei guter Heuristik; bei zulässiger Heuristik bleibt der Weg optimal. Nachteil: hängt stark von der Heuristik ab (schlechte Heuristik ≈ Dijkstra).", "DISCLAIMER": "Diese A*-Implementierung ist bewusst einfach gehalten. Es wird nur in vier Richtungen gegangen und jeder Schritt kostet 1. Die Heuristik ist minimal und dient nur dazu, das Prinzip von A* gegenüber Dijkstra zu demonstrieren. Ziel ist nicht ein optimaler oder produktionsreifer A*-Algorithmus, sondern eine anschauliche Visualisierung, wie Heuristiken die Suche beschleunigen können." }, "ALERT": { "START_END_NODES": "Bitte wählen Sie einen Start- und Endknoten aus, bevor Sie den Algorithmus starten." }, "HOW_IT_WORKS": { "INTRO": "Das Gitter ist ein zweidimensionales Array aus Knoten. Jeder Knoten speichert seine Distanz vom Start, seinen Vorgänger und für A* die Heuristik h und die Summe f = g + h. Beide Algorithmen laufen vorab komplett durch und liefern die Liste der besuchten Knoten und den Pfad. Die Animation spielt diese Liste danach Knoten für Knoten mit setTimeout ab.", "ITEM_1": "Dijkstra: alle Knoten liegen in einer Liste. Dijkstra sortiert in jedem Schritt die ganze Liste nach Distanz (O(n² log n)) und entnimmt den nächsten Knoten. Ein Heap wäre schneller; bei höchstens 22.500 Knoten reicht es hier. Hat der nächste Knoten die Distanz unendlich, ist das Ziel nicht erreichbar.", "ITEM_2": "A*: gleiche Struktur, aber nur die offene Liste wird sortiert, nach f = g + h; bei gleichem f gewinnt das kleinere h. Heuristik ist die Manhattan-Distanz. Sie ist zulässig, weil jeder Schritt 1 kostet und es nur vier Richtungen gibt. Darum bleibt der Pfad optimal.", "ITEM_3": "Der Pfad entsteht am Ende aus der Vorgänger-Kette, vom Ziel zurück zum Start.", "ITEM_4": "Vergleich: Start, Ziel und Wände werden in ein zweites Gitter kopiert. A* läuft auf dem ersten, Dijkstra auf dem zweiten, beide Animationen laufen gleichzeitig. Die Zahl der besuchten Knoten zeigt den Unterschied.", "ITEM_5": "Rechenzeit ist die Zeit der Suche ohne Animation. Speicher: ein Knotenobjekt pro Zelle, maximal 150 × 150 = 22.500 Knoten." } }, "SORTING": { "TITLE": "Sortieralgorithmen", "ALGORITHM": "Algorithmen", "START": "Sortierung starten", "RESET": "Zurücksetzen", "GENERATE_NEW_ARRAY": "Neues Array generieren", "SOUND_ON": "Ton an", "SOUND_OFF": "Ton aus", "EXECUTION_TIME": "Ausführungszeit", "ARRAY_SIZE": "Anzahl der Balken", "EXPLANATION": { "TITLE": "Algorithmen", "BUBBLE_SORT_EXPLANATION": "vergleicht wiederholt benachbarte Elemente und tauscht sie, wenn sie in der falschen Reihenfolge stehen. Das größte Element \"blubbert\" dabei wie eine Luftblase ans Ende der Liste. Vorteil: Extrem einfach zu verstehen und zu implementieren; erkennt bereits sortierte Listen sehr schnell. Nachteil: Sehr ineffizient bei großen Listen (Laufzeit O(n²)). In der Praxis kaum genutzt.", "QUICK_SORT_EXPLANATION": "folgt dem \"Teile und Herrsche\"-Prinzip. Ein \"Pivot\"-Element wird gewählt, und das Array wird in zwei Hälften geteilt: Elemente kleiner als das Pivot und Elemente größer als das Pivot. Vorteil: Im Durchschnitt einer der schnellsten Sortieralgorithmen (O(n log n)); benötigt keinen zusätzlichen Speicher (In-Place). Nachteil: Im schlechtesten Fall (Worst Case) langsam (O(n²)), wenn das Pivot ungünstig gewählt wird. Ist nicht stabil (ändert Reihenfolge gleicher Elemente).", "HEAP_SORT_EXPLANATION": "organisiert die Daten zunächst in einer speziellen Baumstruktur (Binary Heap). Das größte Element (die Wurzel) wird entnommen und ans Ende sortiert, dann wird der Baum repariert. Vorteil: Garantiert eine schnelle Laufzeit von O(n log n), selbst im schlechtesten Fall. Benötigt fast keinen zusätzlichen Speicher. Nachteil: In der Praxis oft etwas langsamer als Quick Sort, da die Sprünge im Speicher (Heap-Struktur) den CPU-Cache schlechter nutzen.", "TIMSORT_EXPLANATION": "ist ein hybrider Sortieralgorithmus, der aus Merge Sort und Insertion Sort kombiniert ist. Er unterteilt das Array in kleine 'Runs' und sortiert jeden davon mit Insertion Sort, um sie anschließend schrittweise mit Merge Sort zusammenzuführen. Vorteil: Extrem effizient bei realen Daten, die oft teilweise sortiert sind – O(n log n) im schlechtesten und O(n) im besten Fall. Er ist der Standardalgorithmus in Python und Java. Nachteil: Komplexer zu implementieren als ein reiner Algorithmus und benötigt zusätzlichen Speicher für den Merge-Schritt.", "COCKTAIL_SORT_EXPLANATION": "(auch Shaker Sort) ist eine Erweiterung des Bubble Sort. Statt nur von links nach rechts zu gehen, wechselt er bei jedem Durchlauf die Richtung und schiebt abwechselnd das größte Element nach rechts und das kleinste nach links. Vorteil: Schneller als Bubble Sort, da kleine Elemente am Ende schneller nach vorne wandern (\"Schildkröten-Problem\" gelöst). Nachteil: Bleibt in der Laufzeitklasse O(n²), also für große Datenmengen ineffizient.", "DISCLAIMER": "Die Wahl des \"besten\" Sortieralgorithmus hängt stark von den Daten und den Rahmenbedingungen ab. In der Informatik betrachtet man oft drei Szenarien:", "DISCLAIMER_1": "Best Case: Die Daten sind schon fast sortiert (hier glänzt z.B. Bubble Sort).", "DISCLAIMER_2": "Average Case: Der statistische Normalfall.", "DISCLAIMER_3": "Worst Case: Die Daten sind maximal ungünstig angeordnet (hier bricht Quick Sort ohne Optimierung ein, während Heap Sort stabil bleibt).", "DISCLAIMER_4": "Zusätzlich gibt es fast immer einen Time-Space Trade-off (Zeit-Speicher-Kompromiss): Algorithmen, die extrem schnell sind (wie Merge Sort), benötigen oft viel zusätzlichen Arbeitsspeicher. Algorithmen, die direkt im vorhandenen Speicher arbeiten (wie Heap Sort), sparen Platz, sind aber manchmal komplexer oder minimal langsamer. Es gibt also keine \"One-Size-Fits-All\"-Lösung." }, "HOW_IT_WORKS": { "INTRO": "Jeder Algorithmus läuft zuerst komplett durch und legt bei jedem Vergleich und jedem Tausch eine Kopie des Arrays (Snapshot) in eine Liste. Die Animation spielt die Snapshots danach mit 100 ms pro Schritt ab. Die Rechenzeit ist die Zeit für den Durchlauf samt Kopien, nicht die Animation. Vergleiche und Schreibzugriffe zählen während der Animation hoch.", "ITEM_1": "Bubble Sort und Cocktail Sort: O(n²) Vergleiche, in-place. Cocktail Sort wechselt pro Durchlauf die Richtung und stoppt, sobald ein Durchlauf nichts mehr tauscht.", "ITEM_2": "Quick Sort: Lomuto-Partition mit dem letzten Element als Pivot, rekursiv. Durchschnitt O(n log n), schlechtester Fall O(n²), zum Beispiel bei schon sortierten Daten.", "ITEM_3": "Heap Sort: baut einen Max-Heap im Array auf (heapify von der Mitte nach vorn) und tauscht die Wurzel ans Ende. Immer O(n log n), kein Zusatzspeicher.", "ITEM_4": "Tim Sort (vereinfacht): Insertion Sort auf festen Blöcken von 32 Elementen, dann Bottom-up-Merge mit Hilfsarrays. Ohne Run-Erkennung und Galloping wie im Original. Bei 200 Balken sind das 7 Blöcke und 3 Merge-Ebenen.", "ITEM_5": "Speicher: die Snapshot-Liste ist der größte Posten, O(Schritte · n). Bubble Sort mit 200 Balken erzeugt bis zu etwa 40.000 Snapshots mit je 200 Einträgen." } }, "GOL": { "TITLE": "Conway's Spiel des Lebens", "START": "Starten", "PAUSE": "Pause", "RANDOM_SCENE": "Zufällig", "EMPTY_SCENE": "Leer", "SIMPLE_SCENE": "Gleiter", "PULSAR_SCENE": "Pulsar", "GUN_SCENE": "Pistole", "ALIVE": "Lebend", "DEAD": "Leer", "SPEED": "Zeit pro Generation", "EXPLANATION": { "TITLE": "Erklärung", "EXPLANATION": "Das Spiel läuft schrittweise ab. Zunächst wird eine Anfangsgeneration von lebenden Zellen auf dem Spielfeld definiert. Aus der vorliegenden Generation (dem Gesamtbild des Spielfeldes) wird die Folgegeneration ermittelt. Der Zustand jeder einzelnen Zelle in der Folgegeneration ergibt sich dabei nach einfachen Regeln aus ihrem aktuellen Zustand sowie den aktuellen Zuständen ihrer acht Nachbarzellen (Moore-Nachbarschaft).", "DISCLAIMER": "Nach Conways ursprünglicher Regel lebt eine Zelle in der nächsten Runde, wenn zuvor in ihrer 3x3-Umgebung insgesamt genau drei Zellen leben, wobei sie selbst nur bei Bedarf mitgezählt wird, das heißt:", "DISCLAIMER_1": "Eine lebende Zelle lebt auch in der Folgegeneration, wenn sie entweder zwei oder drei lebende Nachbarn hat.", "DISCLAIMER_2": "Eine tote Zelle „wird geboren“ (lebt in der Folgegeneration), wenn sie genau drei lebende Nachbarn hat.", "DISCLAIMER_3": "Eine lebende Zelle „stirbt“ (ist in der Folgegeneration tot), wenn sie weniger als zwei (Vereinsamung) oder mehr als drei (Übervölkerung) lebende Nachbarn hat.", "DISCLAIMER_4": " Eine tote Zelle bleibt tot, wenn sie nicht genau drei lebende Nachbarn hat." }, "HOW_IT_WORKS": { "INTRO": "Das Spielfeld sind zwei Gitter aus Zellen mit einem alive-Flag: ein Lese- und ein Schreibgitter (Double Buffering). Jede Generation liest nur aus dem einen und schreibt nur in das andere, danach werden beide getauscht. So beeinflusst eine schon geänderte Zelle ihre Nachbarn nicht in derselben Generation.", "ITEM_1": "Pro Zelle werden die acht Nachbarn gezählt (Moore-Nachbarschaft). Der Rand wird abgeschnitten, nicht umgebrochen: Randzellen haben weniger Nachbarn, das Feld ist kein Torus.", "ITEM_2": "Laufzeit pro Generation O(Zeilen · Spalten · 8). Bei 200 × 200 sind das 40.000 Zellen und 320.000 Nachbar-Prüfungen.", "ITEM_3": "Die Schleife ist eine async-Schleife: rechnen, zeichnen, dann warten, bis die eingestellte Zeit pro Generation voll ist. Ändert sich keine Zelle mehr, stoppt das Spiel von selbst.", "ITEM_4": "Gezeichnet wird das ganze Gitter auf ein Canvas: eine Füll-Operation für den Hintergrund plus eine pro lebender Zelle." } }, "LABYRINTH": { "TITLE": "Labyrinth-Erzeugung", "PRIM": "Erzeuge Prim's Labyrinth", "KRUSKAL": "Erzeuge Kruskal's Labyrinth", "EXPLANATION": { "TITLE": "Algorithmen", "PRIM_EXPLANATION": "startet an einem zufälligen Punkt und erweitert das Labyrinth, indem er immer eine zufällige benachbarte Wand zu einer bereits besuchten Zelle auswählt und diese öffnet. Vorteil: Erzeugt sehr gleichmäßige, natürlich wirkende Labyrinthe mit vielen kurzen Sackgassen. Visuell wirkt es wie ein organisches Wachstum von einem Zentrum aus.", "KRUSKAL_EXPLANATION": "betrachtet alle Wände des Gitters als potenzielle Wege. Er wählt zufällig Wände aus und öffnet sie nur dann, wenn die beiden angrenzenden Zellen noch nicht miteinander verbunden sind (verhindert Kreise). Vorteil: Erzeugt ein sehr komplexes Labyrinth mit vielen langen, verwinkelten Pfaden. Visuell ist es spannend, da das Labyrinth an vielen Stellen gleichzeitig entsteht und am Ende zu einem Ganzen verschmilzt.", "DISCLAIMER": "Beide Algorithmen basieren auf dem Prinzip des 'Minimal Spanning Tree' (Minimaler Spannbaum). Das bedeutet für dein Labyrinth:", "DISCLAIMER_1": "Perfektes Labyrinth: Es gibt keine geschlossenen Kreise (Loops) – jeder Punkt ist erreichbar, aber es gibt immer nur genau einen Weg zwischen zwei Punkten.", "DISCLAIMER_2": "Erreichbarkeit: Da es ein Spannbaum ist, wird garantiert jede Zelle des Gitters Teil des Labyrinths, es gibt keine isolierten Bereiche.", "DISCLAIMER_3": "Zufälligkeit: Durch die Gewichtung der Kanten mit Zufallswerten entstehen bei jedem Durchlauf völlig neue, einzigartige Strukturen.", "DISCLAIMER_4": "Anwendung: Solche Labyrinthe sind die perfekte Testumgebung für Pfadfindungsalgorithmen wie Dijkstra oder A*." }, "HOW_IT_WORKS": { "INTRO": "Das Gitter hat 101 × 101 Zellen. Zellen mit geraden Koordinaten sind Räume (51 × 51 = 2.601), die Zellen dazwischen sind Wände. Beide Algorithmen berechnen zuerst die Reihenfolge, in der Wände geöffnet werden; die Animation öffnet sie danach eine nach der anderen. Das Ergebnis ist ein Spannbaum: jeder Raum ist erreichbar, und zwischen zwei Räumen gibt es genau einen Weg.", "ITEM_1": "Prim (randomisiert): startet in einem zufälligen Raum und hält eine Frontier-Liste mit Wänden zu noch nicht besuchten Nachbarräumen. Pro Schritt wird eine zufällige Wand gewählt (Tausch mit dem letzten Element, dann pop: O(1)) und geöffnet, wenn der Raum dahinter noch unbesucht ist. Statt Kantengewichten entscheidet der Zufall.", "ITEM_2": "Kruskal (randomisiert): alle 5.100 Wände zwischen zwei Räumen werden gesammelt und mit Fisher-Yates gemischt. Jeder Raum trägt eine Mengen-ID. Eine Wand wird geöffnet, wenn die Räume verschiedene IDs haben; danach bekommen alle Räume der einen Menge die ID der anderen. Das ist ein Durchlauf über das Gitter pro Vereinigung, einfacher als Union-Find und bei 2.601 Räumen schnell genug.", "ITEM_3": "Start und Ziel liegen in zwei zufälligen Ecken. Dijkstra und A* laufen danach auf demselben Gitter wie auf der Wegfindungs-Seite.", "ITEM_4": "Speicher: ein Knotenobjekt pro Zelle (10.201) plus die Liste der zu öffnenden Zellen." } }, "FRACTAL": { "TITLE": "Fraktale", "ALGORITHM": "Algorithmen", "RESET": "Reset", "COLOR_SCHEME": "Farbschema", "MAX_ITERATION": "Maximale Auflösung", "EXPLANATION": { "TITLE": "Mathematische Kunst", "MANDELBROT_EXPLANATION": "basiert auf der iterativen Formel 'z_{n+1} = z_n^2 + c'. Sie prüft für jeden Punkt in der komplexen Ebene, ob die Zahlenfolge stabil bleibt oder ins Unendliche entkommt. Vorteil: Gilt als 'Apfelmännchen' und Mutter der Fraktale. Sie bietet eine unendliche Vielfalt an selbstähnlichen Strukturen, in die man ewig hineinzoomen kann.", "JULIA_EXPLANATION": "nutzt dieselbe Formel wie Mandelbrot, fixiert jedoch den Parameter 'c' und variiert den Startwert. Je nach Wahl von 'c' entstehen filigrane, wolkenartige Gebilde oder zusammenhanglose 'Staubwolken'. Vorteil: Ermöglicht eine enorme ästhetische Varianz, da jede Koordinate der Mandelbrot-Menge ein völlig eigenes, einzigartiges Julia-Fraktal erzeugt.", "NEWTON_EXPLANATION": "entsteht durch die Visualisierung des Newton-Verfahrens zur Nullstellen-Suche einer komplexen Funktion. Jeder Pixel wird danach eingefärbt, zu welcher Nullstelle der Algorithmus konvergiert. Vorteil: Erzeugt faszinierende, sternförmige Symmetrien und komplexe Grenzen, an denen sich die Einzugsgebiete der Nullstellen auf chaotische Weise treffen.", "BURNING_SHIP_EXPLANATION": "ist eine variation des Mandelbrots, bei der vor jedem Iterationsschritt der Absolutbetrag der Real- und Imaginärteile genommen wird: '(|Re(z)| + i|Im(z)|)^2 + c'. Vorteil: Erzeugt eine markante, asymmetrische Struktur, die einem brennenden Schiff mit Segeln ähnelt. Das Fraktal wirkt düsterer und 'mechanischer' als die klassischen Mengen.", "DISCLAIMER": "Alle diese Fraktale basieren auf dem Prinzip der Iteration und dem Chaos-Effekt. Das bedeutet für deine Visualisierung:", "DISCLAIMER_1": "Unendliche Tiefe: Egal wie weit du hineinzoomst, es erscheinen immer neue, komplexe Strukturen, die dem Ganzen oft ähneln (Selbstähnlichkeit).", "DISCLAIMER_2": "Fluchtzeit-Algorithmus: Die Farben geben meist an, wie schnell eine Folge einen bestimmten Schwellenwert überschreitet – je schneller, desto 'heißer' oder heller die Farbe.", "DISCLAIMER_3": "Komplexe Zahlen: Die Berechnung findet nicht in einem normalen Koordinatensystem statt, sondern in der komplexen Ebene mit realen und imaginären Anteilen.", "DISCLAIMER_4": "Rechenintensität: Da für jeden Pixel hunderte Berechnungen durchgeführt werden, sind Fraktale ein klassischer Benchmark für die Performance von Grafikprozessoren (GPUs).", "DISCLAIMER_BOTTOM": "Grafikkarten rechnen standardmäßig mit 32-Bit Fließkommazahlen (float). Diese haben nur etwa 7 Stellen Genauigkeit. Bei sehr hohem Zoom (> 100.000) ist der Unterschied zwischen zwei Pixeln so winzig, dass die Grafikkarte ihn nicht mehr darstellen kann. Sie berechnet für 10 Pixel nebeneinander exakt denselben Wert -> Du siehst Blöcke oder Treppenstufen." }, "HOW_IT_WORKS": { "INTRO": "Das Bild ist ein Fragment-Shader (GLSL) auf einem bildschirmfüllenden Rechteck in Babylon.js. Jeder Pixel rechnet unabhängig seine Folge: für Mandelbrot, Julia und Burning Ship z → z² + c, bis |z|² > 4 oder die maximale Iterationszahl erreicht ist (Fluchtzeit-Algorithmus). Newton iteriert z → z − (z³ − 1) / (3z²) und färbt nach der Nullstelle, gegen die der Pixel konvergiert.", "ITEM_1": "Die CPU schickt pro Frame nur Uniforms: Mittelpunkt, Zoom, maximale Iterationen, Algorithmus, Farbschema und die Julia-Konstante. Die GPU rechnet alle Pixel parallel; Aufwand O(Pixel · Iterationen).", "ITEM_2": "Die maximale Iterationszahl wächst mit dem Zoom: 100 + 200 · log10(Zoom), also bis etwa 1.060 beim maximalen Zoom 64.000.", "ITEM_3": "Zoom per Mausrad hält den Punkt unter dem Cursor fest: sein Weltpunkt wird vor und nach dem Zoom gleich gesetzt und der Mittelpunkt entsprechend verschoben.", "ITEM_4": "Die Farbe kommt aus dem Verhältnis Iterationen / Maximum; Punkte, die nie entkommen, bleiben schwarz.", "ITEM_5": "Unter WebGPU übersetzt Babylon.js denselben GLSL-Shader nach WGSL; es gibt keinen eigenen Compute-Pfad." } }, "FRACTAL3D": { "TITLE": "3D Fraktale", "ALGORITHM": "Algorithmen", "MANDELBULB": "Mandelbulb", "MANDELBOX": "Mandelbox", "JULIA": "Julia", "EXPLANATION": { "TITLE": "3D Fraktale Welten", "MANDELBULB_EXPLANATION": "gilt als der 'Heilige Gral' der 3D-Fraktale. Da komplexe Zahlen nur zweidimensional sind, nutzt dieses Fraktal sphärische Koordinaten und hohe Potenzen (meist v^8 + c), um die Mandelbrot-Menge in den Raum zu projizieren. Vorteil: Es entsteht eine organische, extrem detaillierte Struktur, die an Pflanzen, Korallen oder außerirdische Landschaften erinnert.", "MANDELBOX_EXPLANATION": "basiert nicht auf glatten Kurven, sondern auf geometrischem 'Falten' und Skalieren (Box-Folding & Sphere-Folding). Der Raum wird wie Papier immer wieder gefaltet und gespiegelt. Vorteil: Erzeugt streng geometrische, mechanisch wirkende Strukturen, die wie endlose futuristische Städte, der Borg-Würfel oder komplexe Sci-Fi-Architektur aussehen.", "JULIA_EXPLANATION": "ist das 3D-Pendant zur 2D-Julia-Menge. Während der Mandelbulb eine 'Karte' aller Fraktale ist, fixiert man hier den Parameter 'c' und variiert den Startpunkt zudem variiert es mit der Zeit. Vorteil: Anders als der massive Mandelbulb sind Julia-Bulbs oft hohle, komplexe Tunnelsysteme oder blasenartige Strukturen, die sich perfekt eignen, um mit der Kamera hindurchzufliegen.", "DISCLAIMER": "Diese Visualisierung nutzt eine Technik namens 'Raymarching' (Sphere Tracing). Das bedeutet:", "DISCLAIMER_1": "Keine Polygone: Es gibt keine Dreiecke oder Gitter. Die Form wird rein mathematisch für jeden Pixel in Echtzeit berechnet.", "DISCLAIMER_2": "Distance Estimation: Der Algorithmus 'tastet' sich mit Lichtstrahlen voran, indem er berechnet, wie weit das nächste Objekt entfernt ist, ohne es sofort zu berühren.", "DISCLAIMER_3": "Unendliche Details: Da die Oberfläche mathematisch definiert ist, verpixelt sie nicht beim Zoom – es erscheinen immer neue Strukturen.", "DISCLAIMER_4": "Licht: Die Tiefe entsteht aus der Oberflächennormale: Flächen, die zur Kamera zeigen, sind heller, plus ein fester Anteil Umgebungslicht. Schatten und Ambient Occlusion werden nicht berechnet." }, "HOW_IT_WORKS": { "INTRO": "Es gibt keine Geometrie. Ein Fragment-Shader schickt pro Pixel einen Strahl von der Kamera in die Szene und tastet ihn mit Sphere Tracing (Raymarching) ab: ein Distance Estimator sagt, wie weit die Oberfläche mindestens entfernt ist, und der Strahl springt um diesen Betrag (mal 0,8 als Sicherheitsfaktor) weiter, maximal 128 Schritte. Vorher begrenzt ein Kugeltest (Radius 6) den Strahl auf den relevanten Bereich.", "ITEM_1": "Mandelbulb: 8 Iterationen in Kugelkoordinaten mit Potenz 8 (z → z⁸ + c). Die Distanz ist 0,5 · ln(r) · r / dr, wobei dr die mitgeführte Ableitung ist.", "ITEM_2": "Mandelbox: 15 Iterationen aus Box-Fold (Spiegeln an ±1), Sphere-Fold (Skalieren nach Radius) und Skalierung 2,8.", "ITEM_3": "Julia-Bulb: wie Mandelbulb, aber mit fester Konstante c, die sich mit der Zeit leicht bewegt. Darum bewegt sich die Form.", "ITEM_4": "Normalen kommen aus vier zusätzlichen Distanzabfragen. Beleuchtung: Licht aus Kamerarichtung plus 40 % Umgebungslicht, keine Schatten, kein Ambient Occlusion. Farbe: Orbit Trap (kleinster Radius während der Iteration) auf eine Cosinus-Palette.", "ITEM_5": "Aufwand pro Frame: Pixel × Strahlschritte × Iterationen. Bei Full HD und Mandelbox im schlimmsten Fall rund 4 Milliarden Iterationsschritte. Das läuft als Fragment-Shader nur auf der GPU; einen CPU-Pfad gibt es nicht." } }, "PENDULUM": { "TITLE": "Doppel-Pendel", "TRAIL_DECAY_TIME": "Spurlänge", "DAMPING": "Dämpfung", "ATTRACTION": "Anziehungskraft", "L1_LENGTH": "Länge L1", "L2_LENGTH": "Länge L2", "M1_MASS": "Masse M1", "M2_MASS": "Masse M2", "POKE_M1": "Schubse M1", "POKE_M2": "Schubse M2", "RESET": "Neustarten", "EXPLANATION": { "TITLE": "Chaostheorie: Das Doppelpendel", "EXPLANATION": "Das Doppelpendel ist eines der bekanntesten und faszinierendsten Beispiele der Physik für ein dynamisches System, das 'deterministisches Chaos' erzeugt. Es besteht schlicht aus einem einfachen Pendel, an dessen unterem Ende ein zweites Pendel befestigt ist. Obwohl die zugrundeliegenden Bewegungsgesetze der klassischen Mechanik streng mathematisch definiert sind, ist das Verhalten des Doppelpendels auf lange Sicht absolut unvorhersehbar. Es gilt in der Physik als das klassische Vorzeigeobjekt für den sogenannten Schmetterlingseffekt.", "DISCLAIMER": "Die Simulation rechnet die Bewegungsgleichungen des Pendels einmal pro gerendertem Frame mit festem Zeitschritt dt = 0,015, auf der GPU (WebGPU) oder als Fallback auf der CPU. Dabei gelten folgende Besonderheiten:", "DISCLAIMER_1": "Extreme Sensitivität: Winzigste Änderungen in den Startbedingungen (z.B. ein Tausendstel Grad Abweichung im Startwinkel oder bei der Masse) führen schon nach kurzer Zeit zu einer völlig anderen, chaotischen Flugbahn.", "DISCLAIMER_2": "Deterministisches Chaos: Die Bewegung wirkt zwar völlig wild und zufällig, ist es aber nicht. Startest du die Simulation mit exakt denselben Werten neu, wird das Pendel zu 100 % denselben Weg fliegen.", "DISCLAIMER_3": "Numerische Integration: Da Computer Zeit nicht stufenlos, sondern in winzigen Schritten (dt) berechnen, entstehen bei jedem Frame winzige mathematische Rundungsfehler. Diese summieren sich auf und beeinflussen das Chaos zusätzlich.", "DISCLAIMER_4": "Energieerhaltung & Reibung: In einem perfekten physikalischen System ohne Widerstand würde das Pendel ewig weiterschwingen. Für eine natürliche Optik nutzt der Algorithmus einen künstlichen Dämpfungsfaktor, der Luftreibung simuliert und das System irgendwann beruhigt.", "DISCLAIMER_BOTTOM": "HINWEIS: Wenn zuviele Impulse in das System gegeben werden, wird die Simulation instabil. Dann hängt das Pendel nur noch runter und es muss neu gestartet werden." }, "HOW_IT_WORKS": { "INTRO": "Die Bewegung folgt den Bewegungsgleichungen des Doppelpendels (Lagrange-Formulierung, Formeln wie im Wikipedia-Artikel). Pro gerendertem Frame wird ein Schritt mit festem Zeitschritt dt = 0,015 gerechnet: erst die Winkelbeschleunigungen a1 und a2, dann die Winkelgeschwindigkeiten (mal Dämpfung, plus Impuls), dann die Winkel (semi-implizites Euler-Verfahren).", "ITEM_1": "WebGPU: ein Compute-Shader mit einem einzigen Thread rechnet den Physikschritt; der Zustand (θ1, θ2, v1, v2) bleibt in einem Storage Buffer auf der GPU. Ein zweiter Compute-Shader (64 Threads pro Workgroup, einer pro Pixel) zeichnet Stangen, Massen und Spur direkt in einen Pixel-Buffer; die Spur verblasst pro Frame um den Faktor Spurlänge. Ein Fragment-Shader zeigt den Buffer an.", "ITEM_2": "Ohne WebGPU: dieselbe Physik in TypeScript auf der CPU. Die Spur entsteht per Ping-Pong zwischen zwei Render-Target-Texturen: ein GLSL-Shader liest das alte Bild, dämpft es und zeichnet die beiden Massen neu; ein zweiter Shader zeigt Spur und Stangen an.", "ITEM_3": "Pixel-Buffer-Kodierung: Intensität 0 bis 1 für Spur 1, +2 für Spur 2, +10 und +20 für die Stangen. So reicht ein Float pro Pixel.", "ITEM_4": "Der Zeitschritt ist fest und nicht an die Bildrate gekoppelt: bei 120 Hz läuft das Pendel doppelt so schnell wie bei 60 Hz. Das Euler-Verfahren erhält die Energie nicht exakt; große Impulse können die Simulation instabil machen (siehe Hinweis)." } }, "CLOTH": { "TITLE": "Stoffsimulation", "WIND_ON": "Wind Einschalten", "WIND_OFF": "Wind Ausschalten", "OUTLINE_ON": "Mesh anzeigen", "OUTLINE_OFF": "Mesh ausschalten", "STIFFNESS": "Steifigkeit", "ELONGATION": "Dehnung", "RESTART_SIMULATION": "Simulation neu starten", "EXPLANATION": { "TITLE": "Echtzeit-Stoffsimulation auf der GPU", "CLOTH_SIMULATION_EXPLANATION_TITLE": "Stoffsimulation", "XPBD_EXPLANATION_TITLE": "XPBD (Extended Position-Based Dynamics)", "GPU_PARALLELIZATION_EXPLANATION_TITLE": "GPU Parallelisierung", "DATA_STRUCTURES_EXPLANATION_TITLE": "Datenstrukturen", "CLOTH_SIMULATION_EXPLANATION": "Stoffsimulationen modellieren Textilien meist als ein Gitter aus Massepunkten (Vertices), die durch unsichtbare Verbindungen zusammengehalten werden. Ziel ist es, physikalische Einflüsse wie Schwerkraft, Wind und Kollisionen in Echtzeit darzustellen, ohne dass das Material zerreißt oder sich unnatürlich wie Gummi dehnt.", "XPBD_EXPLANATION": "XPBD (Extended Position-Based Dynamics) ist ein moderner Algorithmus, der statt Beschleunigungen direkt die Positionen der Punkte manipuliert, um Abstandsbedingungen (Constraints) zu erfüllen. Das 'Extended' bedeutet, dass echte physikalische Steifigkeit unabhängig von der Framerate simuliert wird. Vorteil: Absolut stabil, explodiert nicht und topologische Änderungen (wie das Zerschneiden von Stoff) sind trivial. Nachteil: Es ist ein iteratives Näherungsverfahren und physikalisch minimal weniger akkurat als komplexe Matrix-Löser.", "GPU_PARALLELIZATION_EXPLANATION": "Um zehntausende Punkte parallel auf der Grafikkarte zu berechnen, muss man 'Race Conditions' verhindern – also dass zwei Rechenkerne gleichzeitig denselben Knotenpunkt verschieben. Die Lösung nennt sich 'Independent Sets' (oder Graph Coloring): Die Verbindungen werden in isolierte Gruppen (hier 8 Phasen: 2 horizontal, 2 vertikal, 4 diagonal) unterteilt, in denen sich kein einziger Punkt überschneidet. So kann die GPU jede Gruppe blind und mit maximaler Geschwindigkeit abarbeiten.", "DATA_STRUCTURES_EXPLANATION": "Für maximale GPU-Performance müssen Daten speicherfreundlich ausgerichtet werden (16-Byte-Alignment). Anstatt viele einzelne Variablen zu nutzen, packt man Informationen clever in 4er-Blöcke (vec4). Ein Vertex speichert so z. B. [X, Y, Z, Inverse_Masse]. Hat ein Punkt die inverse Masse 0.0, wird er vom Algorithmus ignoriert und schwebt unbeweglich in der Luft – ein eleganter Trick für Aufhängungen ohne extra Wenn-Dann-Abfragen.", "DISCLAIMER": "XPBD vs. Masse-Feder-Systeme: In der physikalischen Simulation gibt es grundlegende Architektur-Unterschiede beim Lösen der Gleichungen:", "DISCLAIMER_1": "Klassische Masse-Feder-Systeme: Hier werden Kräfte (Hookesches Gesetz) berechnet, die zu Beschleunigungen und schließlich zu neuen Positionen führen. Es gibt zwei Wege, diese mathematisch in die Zukunft zu rechnen (Integration):", "DISCLAIMER_2": "Explizite Löser (z.B. Forward Euler): Sie berechnen den nächsten Schritt stur aus dem aktuellen Zustand. Sie sind leicht zu programmieren, aber bei steifen Stoffen extrem instabil. Die Kräfte schaukeln sich auf und die simulation 'explodiert', sofern man keine winzigen, sehr leistungsfressenden Zeitschritte wählt.", "DISCLAIMER_3": "Implizite Löser (z.B. Backward Euler): Sie berechnen den nächsten Schritt basierend auf dem zukünftigen Zustand. Das ist mathematisch enorm stabil, erfordert aber das Lösen riesiger globaler Matrix-Gleichungssysteme in jedem Frame. Dies ist auf der GPU schwerer zu parallelisieren und bricht zusammen, wenn sich die Struktur ändert (z. B. durch Zerschneiden des Stoffs).", "DISCLAIMER_4": "Der XPBD-Kompromiss: XPBD umgeht dieses komplexe Matrix-Problem völlig, indem es als lokaler Löser arbeitet. Es kombiniert die unbedingte Stabilität eines impliziten Lösers mit der enormen Geschwindigkeit, Parallelisierbarkeit und dynamischen Anpassungsfähigkeit eines expliziten Systems." }, "HOW_IT_WORKS": { "INTRO": "Der Stoff ist ein Gitter aus 100 × 100 = 10.000 Partikeln. Jedes Partikel ist ein vec4 [x, y, z, 1/Masse]; die obere Reihe hat inverse Masse 0 und hängt fest. 39.402 Abstandsbedingungen (Constraints) halten die Partikel zusammen: horizontal, vertikal und zwei Diagonalen gegen Scherung. Pro Frame: Integration (Schwerkraft, Wind, Positionsvorhersage), dann 15 Substeps, in denen jede Bedingung einmal gelöst wird (XPBD), zuletzt die Geschwindigkeit aus der Positionsänderung.", "ITEM_1": "XPBD: pro Bedingung wird die Abweichung C = |pA − pB| − Ruhelänge berechnet und beide Partikel um λ = −C / (wA + wB + α) entlang der Verbindung verschoben, gewichtet mit der inversen Masse. α = Compliance / dt² macht die Steifigkeit unabhängig vom Zeitschritt. Der Regler Dehnung skaliert die Ruhelänge.", "ITEM_2": "GPU (WebGPU): drei Compute-Shader (Integrate, Solve, Velocity) mit 64 Threads pro Workgroup. Alle Daten bleiben in Storage Buffers auf der GPU; der Vertex-Shader liest die Positionen direkt aus dem Buffer. Nichts geht zurück zur CPU.", "ITEM_3": "Graph Coloring: zwei Threads dürfen nie dasselbe Partikel schreiben. Darum sind die Bedingungen in 8 Phasen sortiert (gerade und ungerade Spalten, gerade und ungerade Zeilen, je zwei pro Diagonale). In einer Phase hat kein Partikel zwei Bedingungen, also läuft jede Phase ohne Locks. Pro Frame sind das 15 · 8 = 120 Solver-Dispatches (dazu je einer für Integrate und Velocity).", "ITEM_4": "Ohne WebGPU: dieselben drei Schritte in TypeScript auf der CPU mit identischem Speicherlayout (Float32Array). Die Positionen werden pro Frame in den Vertex-Buffer des Meshes kopiert. 39.402 · 15 = 591.030 Bedingungs-Lösungen pro Frame sind für eine CPU viel, die Bildrate fällt deutlich.", "ITEM_5": "Wind: feste Richtung (5, 0, 15) plus ein Sinus-Flattern aus Position und Zeit. Der Flatter-Anteil gibt zusätzlich leichten Auftrieb (y: Flattern · 2)." } }, "BOIDS": { "TITLE": "Boids & Particle Life", "MODE_BOIDS": "Boids", "MODE_PARTICLE_LIFE": "Particle Life", "RESTART": "Neu starten", "REROLL_MATRIX": "Matrix neu würfeln", "AGENTS": "Agenten", "SEPARATION": "Separation", "ALIGNMENT": "Ausrichtung", "COHESION": "Kohäsion", "VIEW_RADIUS": "Sichtradius", "MAX_SPEED": "Max. Geschwindigkeit", "TYPES": "Typen", "INTERACTION_RADIUS": "Interaktionsradius", "FRICTION": "Reibung", "FORCE_SCALE": "Kraftfaktor", "PREDATOR_HINT": "Bewege die Maus über die Fläche: Die Boids fliehen davor. Die Farbe zeigt die Flugrichtung.", "EXPLANATION": { "TITLE": "Emergentes Verhalten aus lokalen Regeln", "BOIDS_TITLE": "Boids (Schwarmverhalten)", "BOIDS": "Jeder Boid folgt drei Regeln und sieht nur Nachbarn innerhalb seines Sichtradius: Separation (dränge deine Nachbarn nicht), Ausrichtung (fliege wie deine Nachbarn) und Kohäsion (bewege dich zur Mitte deiner Nachbarn). Kein Boid kennt den Schwarm. Trotzdem bilden sich Schwärme, teilen sich und vereinen sich wieder. Die Maus ist ein Fressfeind, vor dem die Boids fliehen.", "PARTICLE_LIFE_TITLE": "Particle Life", "PARTICLE_LIFE": "Partikel haben einen Typ, den man an der Farbe sieht. Eine Zufallsmatrix legt fest, wie stark Typ A von Typ B angezogen oder abgestoßen wird. Sehr nahe Partikel stoßen sich immer ab, damit sie nicht in einem Punkt zusammenfallen. Aus diesen einfachen Paarkräften entstehen Zellen, Ketten und kreisende Gruppen. Mit neu gewürfelter Matrix entsteht eine neue Welt.", "GPU_TITLE": "GPU-Parallelisierung", "GPU": "Jeder Agent braucht den Zustand aller anderen Agenten. Würde man die Agenten direkt im selben Puffer aktualisieren, läsen manche neue und manche alte Werte. Darum hat jedes Bild zwei Durchläufe: Zuerst wird der aktuelle Zustand kopiert, dann liest ein GPU-Thread pro Agent die Kopie und schreibt den neuen Zustand. 64 Threads bilden eine Workgroup.", "DISCLAIMER": "Vereinfachungen in dieser Demo:", "DISCLAIMER_1": "Jeder Agent prüft jeden anderen Agenten (O(N²)). Es gibt kein räumliches Gitter. Darum ist die GPU auf 8192 Agenten und der CPU-Pfad auf 1200 begrenzt.", "DISCLAIMER_2": "Die Welt ist ein Torus: Agenten, die einen Rand verlassen, kommen am gegenüberliegenden Rand wieder herein. Abstände werden auf dem kürzesten Weg gemessen.", "DISCLAIMER_3": "Die Kraftkonstanten sind nach Augenmaß eingestellt, nicht physikalisch. Die Reibung wirkt pro Bild, daher hängt das Aussehen leicht von der Bildrate ab." }, "HOW_IT_WORKS": { "INTRO": "So läuft die Simulation, Schritt für Schritt:", "ITEM_1": "Datenlayout: Positionen liegen als vec4 [x, y, Typ, 0] und Geschwindigkeiten als vec4 [vx, vy, 0, 0] vor, 16 Byte pro Agent, damit die GPU ausgerichtete Blöcke liest.", "ITEM_2": "Jedes Bild führt zwei Compute-Dispatches aus: copy (aktuell nach vorher) und step (vorher lesen, aktuell schreiben). Das Rendering liest immer den aktuellen Puffer.", "ITEM_3": "Der Step-Kernel startet einen Thread pro Agent, 64 Threads pro Workgroup. Jeder Thread durchläuft alle Agenten, die Arbeit wächst also mit N².", "ITEM_4": "Die Welt läuft an den Rändern um (Torus). Abstände zu Nachbarn nutzen den kürzesten Weg, so können Schwärme über den Rand ziehen.", "ITEM_5": "Ohne WebGPU läuft dieselbe Mathematik auf der CPU in JavaScript, begrenzt auf 1200 Agenten. Der GPU-Pfad erlaubt bis zu 8192." } }, "PHYSARUM": { "TITLE": "Physarum (Schleimpilz)", "RESET": "Zurücksetzen", "PRESET": "Voreinstellung", "PRESET_NETWORKS": "Netzwerke", "PRESET_CELLS": "Zellen", "PRESET_LOOPS": "Schleifen", "PRESET_FIBERS": "Fasern", "AGENTS": "Agenten", "SENSOR_ANGLE": "Sensorwinkel (°)", "SENSOR_DISTANCE": "Sensorabstand", "TURN_ANGLE": "Drehwinkel (°)", "STEP_SIZE": "Schrittweite", "DEPOSIT": "Ablage", "DECAY": "Zerfall", "DIFFUSE": "Diffusion", "FOOD_HINT": "Halte die Maus oder einen Finger auf der Zeichenfläche gedrückt, um Futter zu malen. Die Agenten folgen ihm.", "EXPLANATION": { "TITLE": "Transportnetze ohne Plan", "PHYSARUM_TITLE": "Physarum polycephalum", "PHYSARUM": "Der Schleimpilz Physarum polycephalum hat kein Gehirn, verbindet aber Futterquellen mit effizienten Netzen. Das Modell von Jones (2010) bildet das mit einfachen Agenten nach. Jeder Agent liest die Spurkarte an drei Punkten (links, vorne, rechts), dreht sich zur stärksten Stelle, macht einen Schritt und hinterlässt eine Spur. Die Spur breitet sich aus und verblasst. Aus dieser Schleife entstehen Netze.", "MODEL_TITLE": "Spurkarte", "MODEL": "Die Spurkarte ist ein Raster aus Zellen mit Werten von 0 bis 1. In jedem Schritt wird sie mit dem 3×3-Mittelwert der Nachbarn weichgezeichnet (Diffusion) und mit einem Zerfallsfaktor multipliziert. Starke Spuren ziehen mehr Agenten an und werden dadurch stärker. Schwache Spuren verschwinden. Die Karte läuft an den Rändern um.", "GPU_TITLE": "GPU-Parallelisierung", "GPU": "Jedes Bild hat drei Compute-Durchläufe: die Agenten (ein Thread pro Agent), die Diffusion der Karte (ein Thread pro Zelle) und eine Kopie der neuen Karte. Viele Agenten legen gleichzeitig in dieselbe Zelle ab, darum ist die Ablage eine atomare Addition.", "DISCLAIMER": "Vereinfachungen in dieser Demo:", "DISCLAIMER_1": "Ein Simulationsschritt pro Bild: Die Geschwindigkeit hängt von der Bildrate deines Bildschirms ab.", "DISCLAIMER_2": "Es gibt keinen Belegungstest: Ein Agent kann auf eine Zelle ziehen, die schon ein anderer Agent nutzt. Das Modell von Jones hat diesen Test.", "DISCLAIMER_3": "Die Spur ist auf 1 begrenzt, darum ist der Regler für die Ablage eine relative Stärke und keine absolute Menge." }, "HOW_IT_WORKS": { "INTRO": "Pro Bild gibt es drei Durchläufe, auf GPU und CPU in gleicher Reihenfolge: Agenten, Diffusion, Kopie. Standard sind 65.536 Agenten auf einer 512 × 512 Karte mit WebGPU und 10.240 Agenten auf einer 256 × 256 Karte ohne WebGPU.", "ITEM_1": "Agenten: Jeder Agent ist ein vec4 [x, y, Richtung, 0] in Kartenzellen. Er liest die Spur am linken (Richtung + Sensorwinkel), vorderen und rechten (Richtung − Sensorwinkel) Sensor, die einen Sensorabstand entfernt liegen. Er dreht sich um den Drehwinkel zur stärkeren Seite. Sind beide Seiten stärker als vorne, dreht er sich zufällig.", "ITEM_2": "Ablage: Nach dem Schritt addiert der Agent den Ablagewert in seine Zelle. Auf der GPU ist die Spur ein Festkomma-Ganzzahlpuffer (1,0 = 4096) und die Ablage ist ein atomicAdd, damit keine Ablage verloren geht, wenn Threads in einer Zelle zusammentreffen.", "ITEM_3": "Diffusion: Jede Zelle wird zu mix(Zelle, Mittelwert 3×3, Diffusion) × Zerfall und auf 0..1 begrenzt. Solange du auf die Zeichenfläche drückst, bekommen Zellen nahe dem Zeiger zusätzlich +0,5 pro Bild (Futter).", "ITEM_4": "Kopie: Die neue Karte wird in einen zweiten Puffer geschrieben und zurückkopiert, damit keine Zelle einen Wert liest, der im selben Durchlauf schon aktualisiert wurde.", "ITEM_5": "Ohne WebGPU laufen dieselben drei Schritte in TypeScript auf Float32Arrays. Die Karte wird jedes Bild als 8-Bit-Textur hochgeladen und von einem WebGL-Shader angezeigt." } }, "GRAY_SCOTT": { "TITLE": "Reaktions-Diffusion (Gray-Scott)", "RESET": "Zurücksetzen", "PRESET": "Voreinstellung", "PRESET_CORAL": "Koralle", "PRESET_BUBBLES": "Blasen", "PRESET_SPOTS": "Punkte", "PRESET_WORMS": "Würmer", "PRESET_MAZE": "Labyrinth", "FEED": "Zufuhr", "KILL": "Abbau", "DIFFUSE_U": "Diffusion U", "DIFFUSE_V": "Diffusion V", "STEPS_PER_FRAME": "Schritte pro Bild", "PAINT_HINT": "Halte die Maus oder einen Finger auf der Zeichenfläche gedrückt, um Chemikalie V zu malen. Unter dem Zeiger startet neues Wachstum.", "NO_FLOAT_TEXTURES": "Diese GPU kann nicht in Float-Texturen rendern. Die Simulation läuft hier nicht.", "EXPLANATION": { "TITLE": "Muster aus zwei Chemikalien", "RD_TITLE": "Reaktions-Diffusion", "RD": "In einem Reaktions-Diffusions-System breiten sich Stoffe aus (Diffusion) und wandeln sich ineinander um (Reaktion). Alan Turing zeigte 1952, dass so aus einem fast gleichmäßigen Start stabile Muster wie Punkte und Streifen entstehen. Ähnliche Muster gibt es auf Tierfellen und Muscheln.", "MODEL_TITLE": "Gray-Scott-Modell", "MODEL": "Zwei Chemikalien leben auf einem Raster. Chemikalie U kommt mit der Zufuhrrate neu hinzu. Chemikalie V frisst U (U + 2V → 3V) und wird mit der Rate Zufuhr + Abbau entfernt. U breitet sich schneller aus als V. Nur die zwei Zahlen Zufuhr und Abbau entscheiden, ob Punkte, Würmer, Koralle oder ein Labyrinth entstehen.", "GPU_TITLE": "GPU-Ping-Pong", "GPU": "Das Raster ist eine Float-Textur mit U im roten und V im grünen Kanal. Ein Fragment-Shader berechnet einen Schritt für alle Zellen gleichzeitig. Er liest die alte Textur und schreibt eine zweite. Dann tauschen beide Texturen die Rollen.", "DISCLAIMER": "Vereinfachungen in dieser Demo:", "DISCLAIMER_1": "Explizites Euler-Verfahren mit Zeitschritt 1. Die Diffusionsregler enden bei 1, damit die Schritte stabil bleiben.", "DISCLAIMER_2": "Das Raster läuft an den Rändern um (Torus): Muster, die auf einer Seite hinausgehen, kommen auf der anderen zurück.", "DISCLAIMER_3": "Hat die GPU keine float32-Render-Texturen, wird float16 benutzt. Die Muster sind dann gröber." }, "HOW_IT_WORKS": { "INTRO": "Das Raster hat 512 × 512 Zellen. Jedes Bild führt die gewählte Zahl von Schritten aus. Jeder Schritt ist ein Vollbild-Durchlauf eines Fragment-Shaders.", "ITEM_1": "Start: Überall U = 1 und V = 0, im mittleren Bereich zufällige kleine Blöcke mit U = 0,5 und V = 0,25. Zurücksetzen zeichnet diesen Start neu.", "ITEM_2": "Diffusion: Der Laplace-Wert einer Zelle ist die gewichtete Summe der 3×3-Nachbarn minus die Zelle selbst. Die Gewichte sind 0,2 für die vier Seitennachbarn und 0,05 für die vier Ecken.", "ITEM_3": "Reaktion: dU = Du · lapU − U·V² + Zufuhr · (1 − U) und dV = Dv · lapV + U·V² − (Zufuhr + Abbau) · V. Beide Werte werden auf 0..1 begrenzt.", "ITEM_4": "Malen: Solange du auf die Zeichenfläche drückst, wird V nahe dem Zeiger auf mindestens 0,5 angehoben. So startet überall neues Wachstum, wo du malst.", "ITEM_5": "Anzeige: Die Farbe kommt nur aus V, von Dunkelblau bis Hellcyan. Die zwei Texturen tauschen nach jedem Schritt, damit keine Zelle einen Wert liest, der im selben Schritt schon aktualisiert wurde." } }, "GPU": { "WEBGL_FALLBACK": "WebGPU ist nicht verfügbar. WebGL wird als Fallback verwendet. Die Leistung kann eingeschränkt sein.", "NOT_SUPPORTED": "Weder WebGPU noch WebGL konnten initialisiert werden. GPU-Visualisierungen sind nicht verfügbar." }, "ALGORITHM": { "TITLE": "Algorithmen", "PATHFINDING": { "TITLE": "Wegfindung", "DESCRIPTION": "Dijkstra und A* auf einem Gitter, einzeln oder im direkten Vergleich." }, "SORTING": { "TITLE": "Sortierung", "DESCRIPTION": "Visualisierung verschiedener Sortieralgorithmen." }, "GOL": { "TITLE": "Conway's Game of Life", "DESCRIPTION": "Das 'Spiel des Lebens' ist ein vom Mathematiker John Horton Conway 1970 entworfenes Spiel." }, "LABYRINTH": { "TITLE": "Labyrinth-Erzeugung", "DESCRIPTION": "Labyrinth-Erzeugung mit Prim und Kruskal, danach Wegfindung darin." }, "FOUR_COLOR": { "TITLE": "Vier-Farben-Satz", "DESCRIPTION": "Der Vier-Farben-Satz besagt, dass jede Landkarte in der Ebene mit maximal vier Farben so eingefärbt werden kann, dass keine zwei aneinandergrenzenden Gebiete dieselbe Farbe besitzen." }, "FOURIER": { "TITLE": "Fourier-Epizykel", "DESCRIPTION": "Jede geschlossene Form wird von einer Kette rotierender Kreise gezeichnet. Zeichne eine eigene Form und sieh zu, wie die Kreise sie mit einer diskreten Fourier-Transformation nachbauen." }, "FRACTAL": { "TITLE": "Fraktale", "DESCRIPTION": "Visualisierung von komplexe, geometrische Mustern, die sich selbst in immer kleineren Maßstäben ähneln (Selbstähnlichkeit)." }, "FRACTAL3D": { "TITLE": "Fraktale 3D", "DESCRIPTION": "3D-Visualisierung von komplexe, geometrische Mustern, die sich selbst in immer kleineren Maßstäben ähneln (Selbstähnlichkeit)." }, "PENDULUM": { "TITLE": "Doppel-Pendel", "DESCRIPTION": "Chaotisches Doppelpendel, Physik im WebGPU-Compute-Shader (Fallback WebGL + CPU)." }, "CLOTH": { "TITLE": "Stoffsimulation", "DESCRIPTION": "Stoffsimulation mit XPBD auf der GPU (WebGPU, Fallback WebGL + CPU)." }, "BOIDS": { "TITLE": "Boids & Particle Life", "DESCRIPTION": "Schwarmverhalten und emergentes Verhalten aus lokalen Regeln, berechnet auf der GPU (WebGPU, Fallback WebGL + CPU)." }, "PHYSARUM": { "TITLE": "Physarum (Schleimpilz)", "DESCRIPTION": "Transportnetze eines Schleimpilzes aus einfachen Agenten und einer Spurkarte, berechnet auf der GPU (WebGPU, Fallback WebGL + CPU)." }, "GRAY_SCOTT": { "TITLE": "Reaktions-Diffusion (Gray-Scott)", "DESCRIPTION": "Punkte, Würmer und Korallen aus zwei reagierenden Chemikalien, berechnet auf der GPU mit Float-Texturen (WebGPU und WebGL)." }, "NOTE": "HINWEIS", "GRID_HEIGHT": "Höhe", "GRID_WIDTH": "Breite", "HOW_IT_WORKS": "So funktioniert es", "SOURCE": "Quellcode" }, "FOUR_COLOR": { "TITLE": "Vier-Farben-Satz", "GENERATE": "Neue Karte generieren", "SOLVE": "Automatisch lösen", "CLEAR": "Farben zurücksetzen", "COLOR_1": "Farbe 1", "COLOR_2": "Farbe 2", "COLOR_3": "Farbe 3", "COLOR_4": "Farbe 4", "EXECUTION_TIME": "Ausführungszeit", "STATUS": { "LABEL": "Status", "INCOMPLETE": "Die Karte ist noch nicht vollständig eingefärbt.", "SOLVED": "Glückwunsch! Du hast die Karte korrekt gelöst!", "CONFLICTS": "Achtung: Benachbarte Regionen haben die gleiche Farbe!", "INVALID": "Karte ist vollständig, enthält aber Fehler." }, "EXPLANATION": { "TITLE": "Der Vier-Farben-Satz", "EXPLANATION": "Der Vier-Farben-Satz besagt, dass jede Landkarte in der Ebene mit maximal vier Farben so eingefärbt werden kann, dass keine zwei aneinandergrenzenden Gebiete dieselbe Farbe besitzen.", "DISCLAIMER": "Dieser Algorithmus verwendet Backtracking, um eine gültige Färbung für die generierten Regionen zu finden.", "DISCLAIMER_1": "Kartengenerierung: Regionen werden mittels eines zufälligen Seed-Wachstumsalgorithmus (Voronoi-ähnlich) auf einem Gitter erzeugt.", "DISCLAIMER_2": "Adjazenz: Zwei Regionen gelten als benachbart, wenn sie mindestens eine gemeinsame Kante im Gitter teilen.", "DISCLAIMER_3": "Backtracking: Der Löser probiert die Farben 1-4 für jede Region aus und macht Schritte rückgängig (Backtracking), wenn ein Konflikt auftritt.", "DISCLAIMER_4": "Interaktiv: Sie können auch auf Regionen klicken, um manuell durch die Farben zu wechseln.", "DISCLAIMER_BOTTOM": "Zum einfärben in die Zelle klicken. Durch erneutes klicken ändert sich die Farbe." }, "ALERT": { "NO_SOLUTION": "Keine Lösung gefunden (das sollte bei einer planaren Karte nicht passieren!).", "LIMIT_REACHED": "Keine Lösung innerhalb von {{limit}} Versuchen gefunden. Bitte eine neue Karte erzeugen." }, "HOW_IT_WORKS": { "INTRO": "Die Karte entsteht auf einem Gitter: pro 30 Zellen wird ein Keim an eine zufällige freie Position gesetzt. Alle Keime wachsen gleichzeitig per Breitensuche (eine gemeinsame Warteschlange), bis jede Zelle zu einer Region gehört. Zwei Regionen sind Nachbarn, wenn zwei ihrer Zellen eine Kante teilen.", "ITEM_1": "Datenstruktur: eine Liste von Regionen, jede mit einem Set ihrer Nachbar-IDs (Adjazenzliste). Das Gitter speichert pro Zelle nur Regions-ID und Farbe.", "ITEM_2": "Lösen: Backtracking. Der Löser nimmt immer die am stärksten eingeschränkte Region zuerst (DSATUR): die mit den meisten verschiedenen Farben bei ihren Nachbarn, bei Gleichstand die mit den meisten Nachbarn. Für sie werden die Farben 1 bis 4 probiert; passt keine, geht der Löser einen Schritt zurück. Versuche zählen jede Farbzuweisung, auch die später zurückgenommenen.", "ITEM_3": "Die Standardkarte (25 × 25, 20 Regionen) löst er in unter 1 ms. Meist braucht er genau einen Versuch pro Region. Damit die Seite nie einfriert, gibt er nach 100.000 Versuchen auf (ungefähr 1 von 3.000 Karten in der größten Stufe) und meldet das; dann hilft eine neue Karte.", "ITEM_4": "Prüfen: alle Regionen durchgehen und jede Farbe mit den Nachbarn vergleichen, O(Regionen + Grenzen)." } }, "FOURIER": { "TITLE": "Fourier-Epizykel", "DRAW_HINT": "Zeichne eine geschlossene Form auf die Fläche oder wähle eine Vorlage", "PRESET_HEART": "Herz", "PRESET_STAR": "Stern", "PRESET_SQUARE": "Quadrat", "PRESET_DOG": "Hund", "CIRCLES": "Kreise", "SPEED": "Geschwindigkeit", "SHOW_CIRCLES": "Kreise anzeigen", "HIDE_CIRCLES": "Kreise ausblenden", "EXPLANATION": { "TITLE": "Fourierreihe", "EXPLANATION": "Eine Fourierreihe besagt, dass jede periodische Kurve die Summe rotierender Kreise ist. Jeder Kreis hat einen eigenen Radius, eine eigene Geschwindigkeit und einen eigenen Startwinkel.", "DISCLAIMER": "Diese Demo geht in drei Schritten vor.", "DISCLAIMER_1": "Deine Form wird auf 256 gleichmäßig verteilte Punkte neu abgetastet.", "DISCLAIMER_2": "Jeder Punkt ist eine komplexe Zahl x + iy. Eine direkte diskrete Fourier-Transformation liefert einen Kreis pro Frequenz.", "DISCLAIMER_3": "Die Kreise werden nach Radius sortiert. Der Regler behält nur die größten N Kreise.", "DISCLAIMER_BOTTOM": "Weniger Kreise geben eine glattere Näherung. Mit allen Kreisen läuft die Kurve exakt durch alle 256 Punkte." }, "HOW_IT_WORKS": { "INTRO": "Die gezeichnete Form wird als geschlossenes Polygon behandelt und auf 256 Punkte mit gleichem Bogenabstand neu abgetastet. Jeder Punkt ist eine komplexe Zahl x + i·y. Eine direkte diskrete Fourier-Transformation (zwei verschachtelte Schleifen, O(N²) = 65.536 Schritte) liefert pro Frequenz einen Kreis mit Radius, Startwinkel und Drehzahl. Die Kreise werden nach Radius sortiert.", "ITEM_1": "Frequenzen laufen von 0 bis N−1; alles über N/2 zählt als negative Frequenz, damit Kreise auch rückwärts drehen.", "ITEM_2": "Pro Frame werden die Kreismittelpunkte addiert (O(Kreise)), die Spitze des letzten Kreises kommt an die Spur. Nach einer vollen Periode wird die Spur gelöscht.", "ITEM_3": "Gezeichnet wird auf Canvas 2D mit requestAnimationFrame außerhalb der Angular-Zone. dt ist die Zeit zwischen zwei Frames, darum ist die Geschwindigkeit unabhängig von der Bildrate.", "ITEM_4": "Vorlagen: Herz und Stern sind Formeln, das Quadrat vier Eckpunkte, der Hund ist eine Punktliste, die mit drei Chaikin-Durchläufen (Corner Cutting) geglättet wird.", "ITEM_5": "Weniger Kreise wirken wie ein Tiefpass: nur die größten Radien bleiben, die Form wird weicher." } }, "STOPWATCH": { "TITLE": "Stoppuhr", "START": "Start", "PAUSE": "Pause", "STOP": "Stopp", "RESET": "Zurücksetzen", "INTERVAL_ENABLED": "Signalton bei Intervall", "FIRST_INTERVAL": "Erstes Intervall", "INTERVAL_MINUTES": "Minuten", "INTERVAL_SECONDS": "Sekunden", "SECOND_INTERVAL_ENABLED": "Zweites, abwechselndes Intervall hinzufügen", "INTERVAL_HINT": "Spielt einen kurzen Ton ab, sobald ein Intervall abgelaufen ist. Mit aktiviertem zweiten Intervall wechseln sich beide ab, das zweite startet direkt nach dem ersten.", "INTERVAL_SCHEDULE": "Nächste Signaltöne bei:" }, "NOT_FOUND": { "TITLE": "Seite nicht gefunden", "MESSAGE": "Die gesuchte Seite existiert nicht oder wurde verschoben.", "BACK_HOME": "Zurück zum Start" }, "SEO": { "DEFAULT": { "TITLE": "Andreas Dahm – Playground", "DESCRIPTION": "Portfolio und interaktiver Playground von Andreas Dahm, Senior Software Developer: Algorithmus-Visualisierungen, GPU-Simulationen und persönliche Projekte." }, "ABOUT": { "TITLE": "Über mich – Andreas Dahm", "DESCRIPTION": "Andreas Dahm, Senior Softwareentwickler und Architekt aus München: Erfahrung, Fähigkeiten und Projekte in Full-Stack-Entwicklung, 3D-Simulation, Algorithmen und KI-gestütztem Engineering." }, "PROJECTS": { "TITLE": "Projekte – Andreas Dahm", "DESCRIPTION": "Ausgewählte Projekte von Andreas Dahm: ein Steam-Release, selbst gehostete Infrastruktur, Game Jams und eine wissenschaftliche Diplomarbeit." }, "ALGORITHMS": { "TITLE": "Algorithmen – Andreas Dahm", "DESCRIPTION": "Interaktive Visualisierungen von Algorithmen und Simulationen: Pathfinding, Sortierung, Fraktale, Stoff- und Pendelphysik – direkt im Browser." }, "FOURIER": { "TITLE": "Fourier-Epizykel – Andreas Dahm", "DESCRIPTION": "Interaktive Fourier-Epizykel: Zeichne eine geschlossene Form und sieh zu, wie eine Kette rotierender Kreise sie mit einer diskreten Fourier-Transformation nachzeichnet." }, "IMPRINT": { "TITLE": "Impressum – Andreas Dahm", "DESCRIPTION": "Rechtliche Informationen und Kontaktdaten zur Website Andreas Dahm Playground." }, "PRIVACY": { "TITLE": "Datenschutzerklärung – Andreas Dahm", "DESCRIPTION": "Datenschutzerklärung zur Website Andreas Dahm Playground." }, "STOPWATCH": { "TITLE": "Stoppuhr – Andreas Dahm", "DESCRIPTION": "Eine einfache Online-Stoppuhr mit konfigurierbaren Intervall-Signaltönen." }, "NOT_FOUND": { "TITLE": "Seite nicht gefunden – Andreas Dahm", "DESCRIPTION": "Die angeforderte Seite konnte nicht gefunden werden." } }, "METRICS": { "FPS": "Bilder/s", "BACKEND": "Backend", "STEP_TIME": "Rechenzeit pro Generation", "GENERATION": "Generation", "GENERATIONS_PER_SECOND": "Generationen/s", "COMPARISONS": "Vergleiche", "WRITES": "Schreibzugriffe", "REGIONS": "Regionen", "BORDERS": "Grenzen", "ATTEMPTS": "Farbversuche (Backtracking)", "SAMPLES": "Abtastpunkte", "ZOOM": "Zoom", "MAX_ITERATIONS": "Max. Iterationen", "RAY_STEPS": "Max. Strahlschritte", "TIME_STEP": "Zeitschritt dt", "PARTICLES": "Partikel", "CONSTRAINTS": "Constraints", "PHASES": "Phasen", "SUBSTEPS": "Substeps pro Frame", "AGENTS": "Agenten", "PAIR_CHECKS": "Paarprüfungen pro Bild", "TRAIL_MAP": "Spurkarte", "GRID": "Gitter", "STEPS_PER_SECOND": "Schritte/s" } }