Paper → Comic-Geschichte (PDF)
Verwende dies, wenn der Benutzer ein Paper (PDF, LaTeX, Text) übergibt und nach einem Comic, einer Geschichte, einem "Comix-Magazin", einer Cartoon-Erklärung oder einer unterhaltsamen visuellen Zusammenfassung fragt. Die Ausgabe ist ein A4-PDF, etwa 8 Seiten, vollständig in Code gezeichnet (SVG + HTML → Chromium PDF). Es ist keine Bildgenerierung erforderlich.
1. Das Paper lesen und ein Faktenblatt erstellen (im Kopf oder in einer Notizdatei)
Extrahiere in einfachen Worten:
- Das System / Gerät / der Algorithmus, das/den die Arbeit untersucht (dies wird zum Helden).
- Das Problem oder die schlechte Metrik (Ausfall, Fehler, Verzögerung, Angriff ...) – dies wird zum Bösewicht.
- Die Grundlinie / klassische Methode und die vorgeschlagene Methode (und ggf. Varianten wie UCB, Ablation).
- Die Schlüsselidee, der Satz oder die Erkenntnis und eine alltägliche Metapher dafür (Puzzleteile, Türen, Schlüssel, Straßen...).
- Die Entscheidungsregel oder der Mechanismus, falls vorhanden (Schwellenwerte, Schalter, Gatter).
- Testaufbau (Parameter, Anzahl der Versuche, SNRs...).
- Kopfzahlen, Tabellen, Robustheitschecks, Grenzen, zukünftige Arbeiten, Autoren und Zugehörigkeit.
Regeln: Zahlen exakt wie im Paper kopieren. Niemals Ergebnisse, Datensatznamen oder Behauptungen erfinden. Wenn ein einfacher Wert neu berechnet werden kann (z. B. eine Dimension oder eine Formelausgabe), mit Python überprüfen. Die Begriffe des Papers beibehalten, aber jedes Akronym einmal erklären.
2. Die Charaktere besetzen (Engine-Funktionen)
| Rolle in der Geschichte | Engine-Funktion | Typische Verwendung |
|---|---|---|
| Held (das System) | robot(...) | Farbe, Brust label, antenna=True zeichnet eine Schiene von Anschlüssen auf seinem Kopf |
| Mentor / Erzähler | scientist(...) | erklärt Theorie; headwear="hijab" oder "hair"; point=True |
| Bösewicht (das Problem) | monster(...) | label auf dem Bauch, Stimmungen böse / verletzt |
| Grundlinie / klassische Methode | sergeant(...) | diszipliniert, Lineal, label z. B. "GRID", "BP", "ZF" |
| Vorgeschlagene / adaptive Methode | explorer(...) | Taschenlampenstrahl; zweite Variante mit tool="lantern" und einer anderen Farbe |
| Entscheidungsregel / Schwellwert | gatekeeper(...) | Text auf Schild label + sub (z. B. "D","99") |
Gib dem Helden einen kurzen, freundlichen Namen, der mit dem Thema verknüpft ist (FLUI für eine Fluid-Antenne, DECO für einen Decoder...). Wähle einen einprägsamen Comic-Titel (z. B. "DIE 50 TÜREN").
3. Storyboard (Standard-Bogen mit 8 Seiten — an das Paper anpassen)
- Cover: großer Titel, Untertitel "Wie lernte zu ...", Tagline mit der Hauptlektion, alle Charaktere, Credit-Zeile mit vollständigem Papier-Titel, Autoren, Zugehörigkeit.
- Kap. 1 Die Welt und das Problem: wer der Held ist, was er tun muss, welche Grenzen er hat (Budget, Rauschen, eine HF-Kette...), der Bösewicht. Eine kleine Tafel mit dem physikalischen Modell.
- Kap. 2 Zwei Betrachtungsweisen: Grundlinie vs. vorgeschlagene Methode, wie jede funktioniert (eine Formel in einer Bildunterschrift
math), und die Regeln des fairen Vergleichs. - Kap. 3 Die Wendung / Schlüsselerkenntnis: eine Überraschung (wo die neue Idee versagt oder gewinnt), dann erklärt der Mentor die Theorie mit einer Tafel und einer Tabelle.
- Kap. 4 Der Mechanismus: Metapher-Panels (z. B. Puzzleteile), Beweis-Zahlen, die Entscheidungsregel als Raute mit JA/NEIN-Verzweigungen.
- Kap. 5 Der große Test: Setup-Bildunterschrift, Punktzahl-Tabelle, Heatmap oder Diagramm, Hauptergebnis in einer Mentor-Blase.
- Kap. 6 Robustheit: andere Einstellungen, Sensitivitätstests, der "einzige Fehler" ehrlich erklärt.
- Finale: Feier-Panel, DIE LEKTION-Box, ehrliche Grenzen + zukünftige Arbeiten, SPICKZETTEL (Schlüsselzahlen) und MERKWÜRDIGE BEGRIFFE (Glossar).
Ein kurzes Paper benötigt möglicherweise 6 Seiten; ein langes bis zu 10. Jede Kapitelseite hat 4–6 Panels.
Schreibregeln:
- Einfaches Deutsch (B2-Niveau), freundlich, flüssig: jedes Panel beantwortet die Frage des vorherigen und wirft die nächste auf.
- Eine Idee pro Panel. Blasen maximal ~30 Wörter; Bildunterschriften maximal ~35 Wörter.
- Fett (
<b>) die Schlüsselwörter und Zahlen. Formeln nur inC(..., cls="math")Bildunterschriften, mit<sub>/<sup>. - Jede Zahl auf das Paper zurückführbar. Illustrationen als "typisch" oder "Beispiel" kennzeichnen.
- Die Mentor-Blasen verwenden
cls="mentor"; Ausrufecls="shout".
4. Bauen
- Schreibe die folgende Engine in
comic_engine.pyin einem Arbeitsordner (genau kopieren). - Schreibe
story.py, das die Engine importiert, die Seiten erstellt undrender(pages, "<Title>_Comic_v1.pdf")aufruft. - Führe es aus.
renderschreibt auch 2-seitige PNG-Vorschaublätter in<name>_preview/.
Voraussetzungen: Python mit playwright (Chromium), numpy, PIL, pdftoppm (poppler). Schriftarten Bangers + Comic Neue werden einmalig von npm pack @fontsource/... in COMIC_FONT_DIR (Standard /tmp/comic_fonts) abgerufen; bei Offline-Fallback auf Systemschriften. Fehlt Playwrights Chromium, führe python -m playwright install chromium aus.
Layout-Spickzettel:
- Seite: A4 210 × 297 mm. Nutzbarer Bereich x = 7…203 mm, y = 16…289 mm (Kopfzeile oben). Bundsteg 4 mm. Vollbreites Panel w = 196; halbe Breite w = 96 (x = 7 und x = 107).
- Innerhalb eines Panels SVG-Einheiten = 4 pro mm: ein 96 × 70 mm Panel ist 384 × 280 Einheiten. Halte alle Zeichnungen innerhalb (x < w·4, y < h·4).
- Charaktere werden mit Füßen bei (x, y) gezeichnet; setze Füße ≈ h·4 − 8. Höhen bei s = 1: Roboter ≈ 125 Einheiten, Wissenschaftler ≈ 120, Entdecker ≈ 120, Sergeant ≈ 115, Torwächter ≈ 195; Monster zentriert und ≈ 150 breit.
- Blasen/Bildunterschriften verwenden mm relativ zum Panel. Bei 9,8 pt fasst eine 45 mm breite Blase ~8 Wörter pro Zeile; jede Zeile ≈ 4,3 mm hoch.
- Schwänze:
bl br bc tl tr l r— der Schwanz muss auf den Sprecher zeigen. - Tabellen und Boxen:
RAW(x, y, w, table(...)), oderRAW(..., '<div class="box lesson"><div class="lt">DIE LEKTION</div>...</div>'); Boxen mit<table>ergeben einen Spickzettel, mit<p>ein Glossar. - Bannertext innerhalb eines Panels:
'<div class="banner">DER GROSSE TEST</div>'als Element.
Kleines Beispiel (eine Seite):
from comic_engine import *
p = []
p.append(panel(7, 16, 196, 70,
robot(160, 272, 1.75, "happy") + dot_row(330, 80, 420, n=50, glow=31, h=26),
[C(3, 3, 74, "In einer Stadt voller Funkwellen lebt <b>FLUI</b>..."),
B(100, 44, 92, "Ich habe <b>50 Ports</b> aber nur <b>eine</b> Funkkette!", "tl")]))
p.append(panel(7, 90, 96, 70, scientist(78, 270, 1.4, point=True) + monster(290, 110, 1.05),
[B(3, 3, 50, "Wähle einen schwachen Port und das <b>Ausfallmonster</b> gewinnt.", "br", "mentor"),
B(42, 50, 50, "Unter dem Schwellwert? Du gehörst MIR!", "tr", "shout")]))
pages = [cover("DIE 50 TÜREN", "Wie eine Ein-Ohr-Antenne lernte, <i>wann</i> sie schlau hinschauen muss", art,
"Schau dorthin, wo es zählt ...<br>aber nur, wenn es zählt!", credit_html),
page(2, "Kapitel 1 · Ein Ohr, Fünfzig Türen", p)]
render(pages, "Die_50_Tueren_Comic_v1.pdf")
5. Visuelle Überprüfung (immer)
Öffne jedes Vorschaublatt mit dem Lesewerkzeug und korrigiere vor der Auslieferung:
- Eine Blase oder Bildunterschrift bedeckt ein Gesicht oder eine wichtige Zahl → verschieben.
- Kunst am Panelrand abgeschnitten oder Text unten abgeschnitten → verschieben oder verkleinern.
- Schwanz zeigt auf die falsche Figur → Schwanz ändern.
- Ein Panel ist halb leer → Figur, Requisite oder größeres Diagramm hinzufügen.
- Zahlen stimmen mit dem Paper überein. Erneut rendern, bis sauber (normalerweise 2–3 Runden).
6. Ausliefern
- Dateiname
<Kurztitel>_Comic_v1.pdf; jede spätere Revision erhält v2, v3, ... - Mit SendUserFile senden. Antworte in 3–5 kurzen Zeilen: der Titel, die Besetzung, die Seitenliste und ein Angebot (andere Sprache, andere Charakternamen, mehr oder weniger Seiten).
- Für eine arabische Version: füge
dir="rtl"auf Blasen/Bildunterschriften hinzu und lade eine arabische Schriftart (z. B.npm pack @fontsource/cairo) in das CSS; Zahlen und Formeln LTR belassen.
Engine: comic_engine.py (diese Datei genau schreiben)
"""comic_engine.py — wiederverwendbare Engine für Comic-Magazin-Erklärungen von Forschungspapieren.
Koordinaten: Jedes Panel wird in mm auf einer A4-Seite (210 x 297 mm) platziert. Innerhalb eines Panels
verwendet die SVG-Zeichnung U = 4 Einheiten pro mm (ein 96 x 70 mm Panel hat viewBox 0 0 384 280).
Charaktere werden mit ihren FÜSSEN bei (x, y) gezeichnet; s = Skalierung (1.0 entspricht etwa 30 mm Höhe).
"""
import os, subprocess, glob
import numpy as np
U = 4
INK = "#141414"
FONT_DIR = os.environ.get("COMIC_FONT_DIR", "/tmp/comic_fonts")
# ---------------------------------------------------------------- Schriftarten
def ensure_fonts():
"""Lade Bangers + Comic Neue (via npm) einmal herunter. Fallback auf Systemschriften, wenn offline."""
want = ["bangers-latin-400-normal.woff2", "comic-neue-latin-400-normal.woff2", "comic-neue-latin-700-normal.woff2"]
found = {os.path.basename(p): p for p in glob.glob(f"{FONT_DIR}/**/*.woff2", recursive=True)}
if all(w in found for w in want):
return found
os.makedirs(FONT_DIR, exist_ok=True)
try:
subprocess.run(["npm", "pack", "@fontsource/bangers", "@fontsource/comic-neue"], cwd=FONT_DIR,
check=True, capture_output=True)
for t in glob.glob(f"{FONT_DIR}/*.tgz"):
d = t[:-4]
os.makedirs(d, exist_ok=True)
subprocess.run(["tar", "xzf", t, "-C", d], check=True)
except Exception as e:
print("Schriftarten-Download fehlgeschlagen, verwende Fallbacks:", e)
return {os.path.basename(p): p for p in glob.glob(f"{FONT_DIR}/**/*.woff2", recursive=True)}
# ---------------------------------------------------------------- Charaktere
def robot(x, y, s=1.0, mood="happy", arms="down", color="#29b6f6", label="1 RF", antenna=True, light="#e1f5fe"):
"""Helden-Roboter (das System / Gerät, das die Arbeit untersucht). Stimmungen: happy worried surprised determined joy.
Arme: down up think. antenna=True zeichnet eine Schiene von Anschlüssen auf seinem Kopf."""
mouths = {
"happy": f'<path d="M-11,-75 Q0,-64 11,-75" fill="none" stroke="{INK}" stroke-width="3" stroke-linecap="round"/>',
"worried": f'<path d="M-10,-70 Q0,-78 10,-70" fill="none" stroke="{INK}" stroke-width="3" stroke-linecap="round"/>'
f'<path d="M-18,-97 L-6,-94 M18,-97 L6,-94" stroke="{INK}" stroke-width="3" stroke-linecap="round"/>',
"surprised": f'<ellipse cx="0" cy="-72" rx="4.5" ry="6" fill="{INK}"/>',
"determined": f'<path d="M-9,-72 L9,-72" stroke="{INK}" stroke-width="3" stroke-linecap="round"/>'
f'<path d="M-18,-96 L-6,-92 M18,-96 L6,-92" stroke="{INK}" stroke-width="3.5" stroke-linecap="round"/>',
"joy": f'<path d="M-12,-76 Q0,-60 12,-76 Z" fill="#c62828" stroke="{INK}" stroke-width="2.5"/>',
}
a = {"up": "M-30,-52 L-48,-80 M30,-52 L48,-80", "think": "M-30,-52 L-44,-30 M30,-52 L22,-70"}.get(
arms, "M-30,-52 L-44,-30 M30,-52 L44,-30")
top = ""
if antenna:
dots = "".join(f'<circle cx="{-52 + k*11.5}" cy="-118" r="3" fill="#0277bd"/>' for k in range(10))
top = (f'<rect x="-62" y="-125" width="124" height="14" rx="7" fill="#b2ebf2" stroke="{INK}" stroke-width="3"/>{dots}'
f'<circle cx="{-52 + 6*11.5}" cy="-118" r="6" fill="#ffeb3b" stroke="{INK}" stroke-width="1.5"/>')
else:
top = f'<circle cx="0" cy="-122" r="7" fill="#ffeb3b" stroke="{INK}" stroke-width="2.5"/>'
return f'''<g transform="translate({x},{y}) scale({s})">
<ellipse cx="0" cy="2" rx="40" ry="6" fill="#000" opacity=".15"/>
<rect x="-21" y="-14" width="13" height="14" rx="3" fill="#455a64" stroke="{INK}" stroke-width="3"/>
<rect x="8" y="-14" width="13" height="14" rx="3" fill="#455a64" stroke="{INK}" stroke-width="3"/>
<path d="{a}" stroke="{INK}" stroke-width="11" stroke-linecap="round" fill="none"/>
<path d="{a}" stroke="{color}" stroke-width="6" stroke-linecap="round" fill="none"/>
<rect x="-32" y="-64" width="64" height="52" rx="12" fill="{color}" stroke="{INK}" stroke-width="3"/>
<rect x="-17" y="-54" width="34" height="20" rx="4" fill="#01579b" stroke="{INK}" stroke-width="2"/>
<text x="0" y="-39.5" font-size="10" fill="#fff" text-anchor="middle" font-family="Bangers">{label}</text>
<rect x="-6" y="-116" width="12" height="12" fill="#90a4ae" stroke="{INK}" stroke-width="2.5"/>
<rect x="-31" y="-106" width="62" height="44" rx="15" fill="{light}" stroke="{INK}" stroke-width="3"/>
<ellipse cx="-11" cy="-87" rx="6" ry="8" fill="{INK}"/><ellipse cx="11" cy="-87" rx="6" ry="8" fill="{INK}"/>
<circle cx="-9" cy="-90" r="2.2" fill="#fff"/><circle cx="13" cy="-90" r="2.2" fill="#fff"/>
<circle cx="-22" cy="-76" r="4" fill="#f48fb1" opacity=".7"/><circle cx="22" cy="-76" r="4" fill="#f48fb1" opacity=".7"/>
{mouths[mood]}{top}</g>'''
def scientist(x, y, s=1.0, point=False, mood="smile", flip=False, scarf="#7e57c2", headwear="hijab",
skin="#f1c27d", hair="#3e2723"):
"""Mentor / Erzähler, der erklärt. headwear: 'hijab' oder 'hair'. Stimmungen: smile open serious.
point=True hält einen Zeigestab nach rechts oben (flip=True spiegelt)."""
mouth = {"smile": f'<path d="M-6,-82 Q0,-77 6,-82" fill="none" stroke="{INK}" stroke-width="2.2" stroke-linecap="round"/>',
"open": f'<path d="M-6,-83 Q0,-74 6,-83 Z" fill="#c62828" stroke="{INK}" stroke-width="2"/>',
"serious": f'<path d="M-5,-81 L5,-81" stroke="{INK}" stroke-width="2.2" stroke-linecap="round"/>'}[mood]
arm = (f'<path d="M18,-60 L40,-82" stroke="{INK}" stroke-width="11" stroke-linecap="round"/>'
f'<path d="M18,-60 L40,-82" stroke="#fafafa" stroke-width="6" stroke-linecap="round"/>'
f'<path d="M40,-82 L66,-108" stroke="#6d4c41" stroke-width="3.5" stroke-linecap="round"/>'
f'<circle cx="40" cy="-82" r="5" fill="{skin}" stroke="{INK}" stroke-width="2"/>') if point else \
(f'<path d="M20,-60 L28,-30" stroke="{INK}" stroke-width="11" stroke-linecap="round"/>'
f'<path d="M20,-60 L28,-30" stroke="#fafafa" stroke-width="6" stroke-linecap="round"/>')
if headwear == "hijab":
head = (f'<path d="M-25,-92 Q-30,-66 -15,-62 L15,-62 Q30,-66 25,-92 Z" fill="{scarf}" stroke="{INK}" stroke-width="3"/>'
f'<circle cx="0" cy="-94" r="25" fill="{scarf}" stroke="{INK}" stroke-width="3"/>')
else:
head = f'<path d="M-19,-86 Q-22,-116 0,-114 Q22,-116 19,-86 Q12,-104 0,-102 Q-12,-104 -19,-86 Z" fill="{hair}" stroke="{INK}" stroke-width="2.5"/>'
fl = ' scale(-1,1)' if flip else ''
body = f'''<ellipse cx="0" cy="2" rx="34" ry="6" fill="#000" opacity=".15"/>
<ellipse cx="-11" cy="-2" rx="10" ry="5" fill="#3e2723" stroke="{INK}" stroke-width="2"/>
'''