% ---------------------------------------------------------------------------
%  robotkarol.sty — Robot-Karol-Welten und Blockprogramme für Unterrichtsmaterial
%
%  Copyright (C) 2026  Anselm Wagner  <anselm.wagner@posteo.de>
%
%  This work may be distributed and/or modified under the conditions of
%  the LaTeX Project Public License, either version 1.3c of this license
%  or (at your option) any later version.  The latest version of this
%  license is in  https://www.latex-project.org/lppl.txt  and version
%  1.3c or later is part of all distributions of LaTeX version
%  2008-05-04 or later.
%
%  This work has the LPPL maintenance status `maintained'.
%  The Current Maintainer of this work is Anselm Wagner.
%
%  This work consists of the files robotkarol.sty, robotkarol-doc.tex,
%  robotkarol-beispiel.tex, robotkarol.cwl, README.md and the derived
%  files robotkarol-doc.pdf and robotkarol-beispiel.pdf.
% ---------------------------------------------------------------------------
%  Das Paket zeichnet Welten und Blockprogramme im Stil von Robot Karol
%  Online (https://karol.arrrg.de/) für Arbeitsblätter und Prüfungen.
%  Es benötigt LuaLaTeX.
%
%  Bereitgestellt werden:
%    * karolwelt-Umgebung  — volle Welten (breite x laenge), wahlweise
%      3D-Schrägbild oder 2D-Draufsicht; Objekte: \karol, \ziegel,
%      \marke, \quader (= \wand)
%    * \karolzeile / \karolpaar / \karolgitter / \karollegende
%      — kompakte einzeilige Welten über Zeichenketten.  Sie zeichnen
%      mit denselben Bausteinen wie die karolwelt-Umgebung: eine
%      Zeilen-Welt ist optisch eine karolwelt mit laenge=1.
%    * karolbloecke-Umgebung mit \bbefehl, \bwiederholemal,
%      \bwiederholesolange, \bwiederholeimmer, \bwenndann,
%      \bwenndannsonst, \bluecke, \bkommentar, \baufruf, \banweisung,
%      \bhauptprogramm, \bbedingung, \bist, \bnichtist
%      — Blockprogrammierung
%    * Inline-Blöcke für den Fließtext: \ibefehl, \iaufruf,
%      \ianweisung, \ikommentar, \ihauptprogramm, \iwiederholemal,
%      \iwiederholesolange, \iwiederholeimmer, \iwenndann, \isonst,
%      \ibedingung, \iist, \inichtist, \iluecke.  Sie setzen genau
%      einen Block in die
%      laufende Zeile: Textgrundlinie und Schriftgröße wie der umgebende
%      Text, flachere Verzierungen.  Mehrzeilige Blöcke erscheinen
%      inline nur mit ihrem Kopf.
%    * karolcode-Umgebung + \kb, \kw — Karol-Programme als Text
%    * struktex-Wrapper + \leeranweisung — Struktogramme
%    * \karolsetup — globale Einstellungen (Farben, Feldbreite, Figur)
%    * Druckmodus: \usepackage[druck]{robotkarol} bzw.
%      \karolsetup{druck=true} stellt alle Ansichten auf tonersparende
%      Graustufen um (siehe Abschnitt 3b)
%    * \karolfigur{karol|emma} — Aussehen der Figur in den Welten
%      (Vorgabe: karol; emma hat langes Haar und eine Blume)
%    * \karolschrift / \karolcodeschrift — die Schriften der Zeichnungen
%      auch für eigenen Text (siehe Abschnitt 0)
%    * \karolonline{2t7t} — Verweis auf eine Aufgabe in Robot Karol
%      Online (setzt karol.arrrg.de/#2t7t und verlinkt sie; braucht
%      hyperref, abschaltbar mit der Paketoption hyperref=false)
%    * Start- und Zielzustände: \ziegel[x=2,y=2,neu] und
%      \marke[x=3,y=1,weg] zeichnen, was entstehen bzw. verschwinden
%      soll; zustand=start|ziel|beides wählt die Ansicht, karolweltpaar
%      stellt Start und Ziel nebeneinander (siehe Abschnitt 2)
%
%  NOTATION DER ZEILEN-WELTEN (\karolzeile):
%      >  <  ^  v   Karol mit Blickrichtung; . leeres Feld; Z Ziegel;
%      2…9 Ziegelstapel; M Marke; Q Quader;
%      {…} mehrere Objekte auf einem Feld, z. B. {M>}, {2M}, {3>}.
%      Leerzeichen sind erlaubt: \karolzeile{> Z . {2M} . Q}
% ---------------------------------------------------------------------------
%  ÄNDERUNGEN
%    v3.9  Start- und Zielzustände in der Umgebung karolwelt.  Die
%          Objektbefehle nehmen start/ziel (bzw. neu/weg und die
%          Karol-Verben hinlegen/aufheben und setzen/loeschen) sowie
%          startanzahl/zielanzahl entgegen; der Schlüssel zustand der
%          Umgebung zeigt den Start, das Ziel oder beides mit den
%          Unterschieden.  Dazu aufraeumen für den Aufräumauftrag,
%          rahmen und hervorheben an der Umgebung, die Umgebung
%          karolweltpaar und \karolzustandslegende.  Ohne die neuen
%          Schlüssel ist die Ausgabe pixelgleich zu v3.8.
%    v3.8  Bedingungen mit Zahl wie in Robot Karol Online: die
%          Bedingungsblöcke nehmen eine optionale Anzahl als
%          Zahlenpille entgegen — \bbedingung[2]{IstZiegel} und
%          \ibedingung[2]{NichtIstZiegel} ergeben die Plättchen
%          „IstZiegel <2>" / „NichtIstZiegel <2>".  Dieselbe Zahl
%          reichen \bwenndann, \bwenndannsonst, \bwiederholesolange
%          und ihre Inline-Fassungen als erstes, optionales Argument
%          durch: \bwenndann[2]{IstZiegel}{…}.
%    v3.7  Im Druckmodus sind die Blöcke der eigenen Anweisungen
%          (\baufruf, \banweisung und ihre Inline-Fassungen) 45 Grad
%          schraffiert statt grau gefüllt — der Unterschied zu
%          \bbefehl ist damit auf einen Blick zu sehen.
%          Die Marke sitzt im Schrägbild jetzt mittig auf ihrem Feld.
%          Bis v3.6 fehlte in ihren Bildkoordinaten der Tiefenanteil;
%          sie saß rund 0,034 Feldbreiten zu weit links und berührte
%          auf einem Ziegel dessen linke Kante.  Betrifft die
%          karolwelt-Umgebung und \karolzeile gleichermaßen.
%          \karolonline{2t7t} verweist auf eine Aufgabe in Robot Karol
%          Online: gesetzt wird karol.arrrg.de/#2t7t, verlinkt auf
%          https://karol.arrrg.de/#2t7t.  Dafür lädt das Paket hyperref
%          (am Ende der Präambel); mit der Paketoption hyperref=false
%          unterbleibt das und der Text steht ohne Verweis.
%          Druckmodus: \usepackage[druck]{robotkarol} bzw. zur Laufzeit
%          \karolsetup{druck=true} zeichnet Welten, Zeilen-Welten und
%          Blockprogramme in tonersparenden Graustufen — so hell wie
%          möglich, aber so, dass alles funktional Wichtige erkennbar
%          bleibt (Stapelhöhen, Marken, \bbefehl gegen \baufruf).
%    v3.6  Vorgabefigur ist wieder „karol"; „emma" holt man sich mit
%          \karolfigur{emma} bzw. \karolsetup{figur=emma}.
%          Schriften wie in der Oberfläche von Robot Karol Online:
%          Beschriftungen in Noto Sans, Programmtext in Hack (ersatz-
%          weise DejaVu Sans Mono).  Dafür wird fontspec geladen.  Mit
%          \usepackage[schrift=dokument]{robotkarol} bleibt es bei den
%          Schriften des Dokuments (Ausgabe wie bis v3.5); zur Laufzeit
%          umschaltbar mit \karolsetup{schrift=…, textschrift=…,
%          codeschrift=…}.
%    v3.5  Zweite Figur „emma" (langes Haar, Blume), umschaltbar mit
%          \karolfigur bzw. \karolsetup{figur=…}; die bisherige Figur
%          heißt „karol".
%    v3.4  Anschliff physikalisch statt durch Zerlegen der Kontur: auf
%          die Fläche beschneiden, Rand hell, Rand dunkel nach links
%          oben versetzt darüber.  Jede nach oben oder links zeigende
%          Kante wird dadurch hell, jede nach unten oder rechts
%          zeigende dunkel — Ecken folgen der Blockform exakt und die
%          Striche bleiben im Block, sodass Blöcke ineinanderpassen.
%    v3.3  Anschliff der Blöcke aufgeteilt: Oberseite und linke Seite
%          hell, alles übrige dunkel.
%    v3.2  Blockoptik näher an der Oberfläche: jeder Block ist umrandet
%          (Kontur farbe!80!black, helle Oberkante farbe!70!white), damit
%          auch gleichfarbige Blöcke voneinander abgesetzt sind;
%          Beschriftungen normal statt fett, Pillentext reines Schwarz,
%          der Stecker der Bedingungen ist eine Puzzlenase.
%    v3.1  Himmelsrichtungs-Bedingungen \bist / \bnichtist mit
%          Auswahlmenü (acht Blöcke wie in Robot Karol Online).  Der
%          Name in \baufruf steht jetzt wie im Original in einer
%          Auswahlpille mit Dreieck.
%    v3.0  karolwelt-Umgebung (volle Welten, 3D und 2D).  \karolzeile
%          zeichnet im selben Design und teilt sich die Bausteine mit
%          ihr; Feldnummern stehen vor der vordersten Gitterkante,
%          Hervorhebungen sind magenta (neue Schlüssel: rahmen, hoehe,
%          hervorfarbe, gitterfarbe).  Neu: Inline-Blöcke.  Das Kommando
%          \karolwelt aus v2.x heißt jetzt \karolzeile.
% ---------------------------------------------------------------------------
% Der LaTeX-Kern von 2022-06-01 wird für \ProcessKeyOptions gebraucht
% (Schlüssel-Wert-Paketoptionen, siehe Abschnitt 0).
\NeedsTeXFormat{LaTeX2e}[2022-06-01]
\ProvidesPackage{robotkarol}%
	[2026/09/23 v3.9 Robot-Karol-Welten und Blockprogramme (LuaLaTeX)]
\ifdefined\directlua\else
	\PackageWarning{robotkarol}{Dieses Paket ist fuer LuaLaTeX gedacht}%
\fi

\RequirePackage{expl3}
\RequirePackage{listings}
\RequirePackage{struktex}
\RequirePackage{tikz}
% patterns.meta: Schraffur der Anweisungsbloecke im Druckmodus
% (Abschnitt 3b).  Anders als die alte patterns-Bibliothek laesst sie
% Winkel, Abstand und Strichstaerke frei einstellen.
\usetikzlibrary{patterns.meta}
\RequirePackage{graphicx}

% ===========================================================================
%  0. Schriften
% ===========================================================================
%  Vorgabe sind die Schriften der Oberfläche von Robot Karol Online:
%  Beschriftungen in Noto Sans, Programmtext in Hack.  Beide sind aus den
%  Referenzaufnahmen bestimmt (Noto Sans deckt sich in
%  `Anweisungen.png` pixelgenau mit dem Blocktext; Hack ist die Schrift
%  des Online-Editors, siehe dessen `public/fonts/hack.css`).
%
%  Paketoption  schrift = karol | dokument
%    karol     (Vorgabe)  Das Paket lädt fontspec und benutzt Noto Sans
%                         (liegt in TeX Live) sowie Hack, falls es im
%                         System installiert ist — sonst DejaVu Sans
%                         Mono, aus dem Hack hervorgegangen ist.
%    dokument             Es wird nichts geladen; das Paket setzt seine
%                         Beschriftungen mit \sffamily und den
%                         Programmtext mit \ttfamily, übernimmt also die
%                         Schriften des Dokuments (Verhalten bis v3.5).
%
%  Zur Laufzeit umschaltbar: \karolsetup{schrift=karol|dokument}.  Eigene
%  Schriften über \karolsetup{textschrift=…} und \karolsetup{codeschrift=…}.
%  Titel und Hinweis einer Welt bleiben immer in der Dokumentschrift —
%  sie sind Text des Dokuments, keine Beschriftung der Zeichnung.
% ---------------------------------------------------------------------------
\ExplSyntaxOn

\bool_new:N \g__karol_druck_bool         % Paketoption: Druckmodus?
\bool_new:N \g__karol_href_bool          % Paketoption: hyperref laden?
\bool_gset_true:N \g__karol_href_bool
\bool_new:N \g__karol_fontladen_bool     % Paketoption: Schriften laden?
\bool_gset_true:N \g__karol_fontladen_bool
\bool_new:N \g__karol_fontda_bool        % Karol-Schriften verfügbar?
\tl_new:N \l__karol_schrift_tl           % Umschaltcode für Beschriftungen
\tl_new:N \l__karol_codeschrift_tl       % Umschaltcode für Programmtext

\keys_define:nn { karol / paket }
{
	schrift .choice: ,
	schrift / karol    .code:n = { \bool_gset_true:N  \g__karol_fontladen_bool },
	schrift / dokument .code:n = { \bool_gset_false:N \g__karol_fontladen_bool },
	% Druckmodus (Abschnitt 3b): \usepackage[druck]{robotkarol}
	druck   .bool_gset:N = \g__karol_druck_bool,
	druck   .default:n   = { true },
	% hyperref für \karolonline (Abschnitt 6) laden?
	hyperref .bool_gset:N = \g__karol_href_bool,
	hyperref .default:n   = { true },
}
\ProcessKeyOptions [ karol / paket ]

% Der eigentliche Schalter ist LOKAL: so wirkt \karolsetup{druck=true} in
% einer Gruppe nur dort, und die Paketoption gibt bloß den Anfangswert vor.
\bool_new:N \l__karol_druck_bool
\bool_set_eq:NN \l__karol_druck_bool \g__karol_druck_bool

% fontspec braucht LuaTeX oder XeTeX und bricht sonst hart ab.  Ohne
% \directlua wird deshalb gar nichts geladen — das Paket setzt dann mit
% den Schriften des Dokuments und bleibt wenigstens übersetzbar.
\cs_if_exist:NF \directlua { \bool_gset_false:N \g__karol_fontladen_bool }

\bool_if:NT \g__karol_fontladen_bool
{
	\RequirePackage { fontspec }
	\newfontfamily \__karol_fonttext:
		[
			Extension      = .ttf ,
			UprightFont    = *-Regular ,
			ItalicFont     = *-Italic ,
			BoldFont       = *-Bold ,
			BoldItalicFont = *-BoldItalic ,
			Ligatures      = TeX ,
			Numbers        = { Monospaced , Lining } ,
		]
		{ NotoSans }
	% Hack liegt nicht in TeX Live.  Ist es im System installiert, wird es
	% benutzt — sonst DejaVu Sans Mono, dessen Vorläufer (Bitstream Vera
	% Sans Mono) die Grundlage von Hack ist.
	\IfFontExistsTF { Hack }
	{
		\newfontfamily \__karol_fontmono: [ Ligatures = TeX ] { Hack }
		\PackageInfo { robotkarol } { Programmtext ~ in ~ Hack }
	}
	{
		\newfontfamily \__karol_fontmono:
			[
				Extension      = .ttf ,
				UprightFont    = * ,
				ItalicFont     = *-Oblique ,
				BoldFont       = *-Bold ,
				BoldItalicFont = *-BoldOblique ,
				Ligatures      = TeX ,
			]
			{ DejaVuSansMono }
		\PackageInfo { robotkarol }
			{ Hack ~ nicht ~ gefunden ~ - ~ Programmtext ~ in ~ DejaVu ~ Sans ~ Mono }
	}
	\bool_gset_true:N \g__karol_fontda_bool
}

% Die beiden Umschalter, die überall im Paket benutzt werden.
\cs_new_protected:Npn \__karol_textfont:  { \tl_use:N \l__karol_schrift_tl }
\cs_new_protected:Npn \__karol_monofont:  { \tl_use:N \l__karol_codeschrift_tl }

% Auswahl setzen (auch von \karolsetup{schrift=…} aus aufgerufen)
\cs_new_protected:Npn \__karol_schrift_dokument:
{
	\tl_set:Nn \l__karol_schrift_tl     { \sffamily }
	\tl_set:Nn \l__karol_codeschrift_tl { \ttfamily }
}
\cs_new_protected:Npn \__karol_schrift_karol:
{
	\bool_if:NTF \g__karol_fontda_bool
	{
		\tl_set:Nn \l__karol_schrift_tl     { \__karol_fonttext: }
		\tl_set:Nn \l__karol_codeschrift_tl { \__karol_fontmono: }
	}
	{
		\PackageWarning { robotkarol }
		{
			Die ~ Schriften ~ von ~ Robot ~ Karol ~ Online ~ sind ~ nicht ~
			geladen \MessageBreak
			(Paketoption ~ schrift=dokument ~ oder ~ keine ~ LuaTeX-Engine).
			\MessageBreak
			Es ~ werden ~ die ~ Schriften ~ des ~ Dokuments ~ benutzt
		}
		\__karol_schrift_dokument:
	}
}
\bool_if:NTF \g__karol_fontda_bool
	{ \__karol_schrift_karol: } { \__karol_schrift_dokument: }

% Öffentliche Umschalter — damit eigener Text zur Zeichnung passt.
\NewDocumentCommand { \karolschrift }     { } { \__karol_textfont: }
\NewDocumentCommand { \karolcodeschrift } { } { \__karol_monofont: }

\ExplSyntaxOff

% ===========================================================================
%  1. Karol-Programme  (Monospace, Schlüsselwörter fett)
% ===========================================================================
\lstdefinelanguage{Karol}{%
	morekeywords={wiederhole,mal,endewiederhole,solange,immer,wenn,dann,sonst,endewenn},
	sensitive=true,
}
\lstdefinestyle{karolstil}{%
	language      = Karol,
	basicstyle    = \karolcodeschrift\small,
	keywordstyle  = \bfseries,
	columns       = fullflexible,
	keepspaces    = true,
	tabsize       = 2,
	xleftmargin   = 1em,
	aboveskip     = .7em,
	belowskip     = .7em,
	extendedchars = true,
	literate      = {ö}{{\"o}}1 {ä}{{\"a}}1 {ü}{{\"u}}1
	                {Ö}{{\"O}}1 {Ä}{{\"A}}1 {Ü}{{\"U}}1 {ß}{{\ss}}1,
}
\lstnewenvironment{karolcode}[1][]{\lstset{style=karolstil,#1}}{}

% Karol-Fragmente im Fließtext: \kb{IstZiegel}, Schlüsselwort: \kw{wenn}
\newcommand*{\kb}[1]{{\karolcodeschrift #1}}
\newcommand*{\kw}[1]{{\karolcodeschrift\bfseries #1}}

% ===========================================================================
%  2. Struktogramme  (struktex)
% ===========================================================================
\newcommand*{\leeranweisung}{\ensuremath{\emptyset}}
\newcommand*{\stklucke}[1][2.4cm]{\rule[-0.4ex]{#1}{0.4pt}}
\setlength{\unitlength}{1mm}

% ===========================================================================
%  3. Farbschema  (zentral, per \karolsetup änderbar)
% ===========================================================================
% --- Welt ---
% Gezeichnet wird mit den karolW…-Farben (in Abschnitt 4, pixelgenau aus
% Robot Karol Online gesampelt).  Die vier Namen hier bleiben als
% öffentliche Kurzformen bestehen: \karolsetup{ziegelfarbe=…} setzt sie
% zusammen mit den zugehörigen karolW…-Farben.
\definecolor{karolBoden}{RGB}{45,45,215}          % Bodengitter
\definecolor{karolZiegel}{RGB}{255,0,0}           % Ziegel
\definecolor{karolMarke}{RGB}{250,205,60}         % Marke
\definecolor{karolQuader}{RGB}{128,128,128}       % Quader / Wand
% --- Blöcke (Voreinstellung; an Karol Online anpassbar) ---
% Blockfarben: exakt aus der Karol-Online-Oberfläche gesampelt
% (systematische 91/165-Palette)
\definecolor{karolBlockBefehl}{RGB}{116,91,165}    % Anweisungen (violett)
\definecolor{karolBlockSchleife}{RGB}{91,165,91}   % Schleifen (grün)
\definecolor{karolBlockWenn}{RGB}{91,165,91}       % Verzweigungen (grün)
\definecolor{karolBlockBedingung}{RGB}{91,128,165} % Bedingungen (stahlblau)
\definecolor{karolBlockAnweisung}{RGB}{165,91,98}  % eigene Anweisungen (rot)
\definecolor{karolBlockKommentar}{RGB}{165,150,91} % Kommentare (khaki)
\definecolor{karolBlockHut}{RGB}{130,123,117}      % Hauptprogramm-Hut

\ExplSyntaxOn

% ---------------------------------------------------------------------------
%  \karolsetup{feldbreite=8mm, ziegelfarbe=red!70!black, …}
% ---------------------------------------------------------------------------
\dim_new:N \g__karol_feld_dim               % Kantenlänge eines Feldes
\dim_gset:Nn \g__karol_feld_dim { 8.4mm }

\bool_new:N \l__karol_wemma_bool
\tl_new:N \l__karol_wfigur_tl   % gewählte Figur (vor keys_define: .initial
                                % wird schon beim Definieren ausgeführt)
\keys_define:nn { karol / setup }
{
	feldbreite       .dim_gset:N = \g__karol_feld_dim,
	% Weltfarben.  Sie wirken auf \karolzeile UND die karolwelt-Umgebung,
	% die dieselben Bausteine zeichnen.
	bodenfarbe       .code:n     = { \colorlet{karolBoden}{#1}
	                                 \colorlet{karolWGitter}{#1} },
	gitterfarbe      .meta:n     = { bodenfarbe = #1 },
	% ziegelfarbe setzt die leuchtende Lagenkante; der flachere Körper wird
	% daraus abgeleitet — dasselbe Verhältnis wie im Original (59 %).
	ziegelfarbe      .code:n     = { \colorlet{karolZiegel}{#1}
	                                 \colorlet{karolWZiegelKante}{#1}
	                                 \colorlet{karolWZiegelDrei}{#1!59!black} },
	markenfarbe      .code:n     = { \colorlet{karolMarke}{#1}
	                                 \colorlet{karolWMarke}{#1}
	                                 \colorlet{karolWZelleMarke}{#1} },
	quaderfarbe      .code:n     = { \colorlet{karolQuader}{#1}
	                                 \colorlet{karolWZelleQuader}{#1} },
	hervorfarbe      .code:n     = { \colorlet{karolWHervor}{#1} },
	% Tonersparender Graustufenmodus (Abschnitt 3b).  Wirkt auf alle
	% Ansichten und gilt nur in der Gruppe, in der er gesetzt wird.
	druck    .bool_set:N = \l__karol_druck_bool,
	druck    .default:n  = { true },
	% Aussehen der Figur in \karolzeile und der karolwelt-Umgebung
	figur    .choices:nn = { karol, emma }
		{ \tl_set:Nx \l__karol_wfigur_tl { \l_keys_choice_tl } },
	figur    .initial:n  = { karol },
	% Schriften: karol = Noto Sans / Hack wie in Robot Karol Online,
	% dokument = \sffamily / \ttfamily des Dokuments (siehe Abschnitt 0).
	schrift  .choices:nn = { karol, dokument }
		{ \use:c { __karol_schrift_ \l_keys_choice_tl : } },
	% Freie Wahl: der Wert ist der Umschaltbefehl selbst,
	% z. B. textschrift = { \fontspec{Fira~Sans} }.
	textschrift .tl_set:N = \l__karol_schrift_tl,
	codeschrift .tl_set:N = \l__karol_codeschrift_tl,
	% Feinsteuerung der Ziegel (nach ziegelfarbe zu setzen)
	ziegelkoerperfarbe .code:n   = { \colorlet{karolWZiegelDrei}{#1} },
	ziegelkantenfarbe  .code:n   = { \colorlet{karolWZiegelKante}{#1} },
	befehlfarbe      .code:n     = { \colorlet{karolBlockBefehl}{#1} },
	schleifenfarbe   .code:n     = { \colorlet{karolBlockSchleife}{#1} },
	verzweigungfarbe .code:n     = { \colorlet{karolBlockWenn}{#1} },
	bedingungfarbe   .code:n     = { \colorlet{karolBlockBedingung}{#1} },
	anweisungfarbe   .code:n     = { \colorlet{karolBlockAnweisung}{#1} },
	kommentarfarbe   .code:n     = { \colorlet{karolBlockKommentar}{#1} },
	hutfarbe         .code:n     = { \colorlet{karolBlockHut}{#1} },
}
\NewDocumentCommand { \karolsetup } { m }
	{ \keys_set:nn { karol / setup } { #1 } }

% \karolfigur{karol} (Vorgabe) bzw. \karolfigur{emma} — Kurzform für
% \karolsetup{figur=…}.  Wirkt auf \karolzeile UND die karolwelt-Umgebung;
% in einer Gruppe gesetzt gilt es nur dort.
\NewDocumentCommand { \karolfigur } { m }
	{ \keys_set:nn { karol / setup } { figur = #1 } }

% ===========================================================================
%  3b. Druckmodus  (tonersparende Graustufen)
% ===========================================================================
%  \usepackage[druck]{robotkarol}   — das ganze Dokument
%  \karolsetup{druck=true}          — ab hier bzw. nur in dieser Gruppe
%  \karolsetup{druck=false}         — zurück zur Farbdarstellung
%
%  Leitgedanke: so wenig Toner wie möglich, so viel wie nötig.  Alles,
%  was eine Aufgabe trägt, muss erkennbar bleiben — dass auf einem Feld
%  eine Marke liegt, wie hoch ein Ziegelstapel ist, ob ein Block ein
%  \bbefehl oder ein \baufruf ist.  Alles andere wird weiß.
%
%  Deshalb tragen im Druckmodus die KONTUREN die Information und die
%  Flächen bleiben so hell wie möglich:
%
%    * Ziegel sind weiß mit schwarz umrandeten Lagen — der Stapel bleibt
%      seitlich abzählbar, kostet aber praktisch keine Farbe.
%    * Der Quader ist die einzige große graue Fläche der Welt (er muss
%      sich vom Ziegel unterscheiden, dessen Form er teilt).
%    * Die Marke ist mittelgrau: sie ist klein, und sie liegt oft auf
%      einem Ziegel, von dem sie sich absetzen muss.
%    * Bei den Blöcken bekommt der häufigste — \bbefehl — die weiße
%      Fläche; die formgleichen \baufruf/\banweisung bekommen eine
%      45-Grad-Schraffur auf weißem Grund.  Ein bloß dunkleres Grau
%      trug diesen Unterschied nicht — beide Blöcke haben dieselbe
%      Form, im Original unterscheidet sie nur die Farbe (violett
%      gegen rot).  Beschriftung und Namenspille stehen dabei auf
%      weißem Grund, schraffiert ist nur der Hintergrund; die
%      Schraffur braucht zudem weniger Farbe als eine graue Fläche.
%      Alles übrige unterscheidet
%      sich ohnehin schon durch die Form (C-Maul, Hut, Plättchen).
%
%  Technisch ist der Modus eine zweite Palette: \__karol_farbschema: legt
%  sie zu Beginn jeder Zeichnung über die Farbnamen.  Da \colorlet
%  gruppenlokal wirkt und jede Zeichnung in einer Gruppe läuft, sind
%  eigene Farbeinstellungen danach unverändert — die Farbschlüssel von
%  \karolsetup gelten unbesehen weiter und wirken wieder, sobald der
%  Druckmodus aus ist.  Umgekehrt haben sie im Druckmodus keine Wirkung:
%  eigene Farben würden das Graustufenschema gerade aufheben.
% ---------------------------------------------------------------------------
% Rand der Eingabepillen.  Im Farbmodus hebt sich die Pille durch ihre
% Tönung vom Block ab und braucht keinen — im Druck ist sie auf einem
% weißen \bbefehl selbst weiß, dort trägt erst die Kontur die Information,
% dass hier ein Eingabefeld sitzt.
%
% Als STIL, nicht als tl mit Schlüsselliste: pgfkeys zerlegt eine
% Optionsliste, bevor Makros expandiert werden — ein tl mit Komma darin
% wäre ein einziger, unbekannter Schlüsselname.  Und im Farbmodus darf
% wirklich nichts gesetzt werden, auch keine line~width: sie verstellt
% TikZs outer~sep (Vorgabe 0.5\pgflinewidth) und damit den Anker
% base~east, an dem das Auswahldreieck hängt.
\tikzset{ karolpillenrand/.style = { draw = none } }

% Zwei Rollenfarben, die es nur im Druck gibt (per \definecolor änderbar):
\definecolor{karolDruckKontur}{gray}{0}      % Blockkonturen, Pillenrand
\definecolor{karolDruckAkzent}{gray}{0.35}   % Auswahldreieck der Pillen
\definecolor{karolDruckSchraffur}{gray}{0}   % Schraffur der Anweisungen

% Welche Blockfarben werden im Druck schraffiert statt grau gefuellt?
% (Die Namen bleiben stabil, auch wenn \karolsetup{anweisungfarbe=...}
% die Farbe dahinter aendert.)
\clist_const:Nn \c__karol_bschraffur_clist { karolBlockAnweisung }

% Ist die Druckpalette in dieser Gruppe schon gelegt?  Verschachtelte
% Blöcke erben das Register und sparen sich die Wiederholung.
\bool_new:N \l__karol_druckda_bool

\cs_new_protected:Npn \__karol_druckfarben:
{
	% --- Welt ---------------------------------------------------------
	\colorlet { karolBoden }        { black!45 }
	\colorlet { karolWGitter }      { black!45 }
	% Ziegel: weißer Körper, schwarze Lagenkanten.  Genau wie im Original
	% ist damit jede Lage einzeln zu sehen und der Stapel abzählbar.
	\colorlet { karolZiegel }       { black }
	\colorlet { karolWZiegel }      { white }
	\colorlet { karolWZiegelDrei }  { white }
	\colorlet { karolWZiegelKante } { black }
	% Das Kreuz trägt im Druck die Aussage „soll weg" allein — schwarz,
	% nicht grau, sonst geht es auf dem weissen Ziegel unter.
	\colorlet { karolWKreuz }       { karolDruckKontur }
	% Der Pfeil trägt im Druck die Aussage „kommt noch dazu" — weiße
	% Fläche, aber durchgezogene Kontur, während der Mini-Ziegel darunter
	% gestrichelt wird.
	\colorlet { karolWPfeil }       { white }
	\colorlet { karolWPfeilKante }  { karolDruckKontur }
	\colorlet { karolWDiff }        { karolDruckKontur }
	\colorlet { karolWFuge }        { black!55 }
	% Marke: mittelgrau, damit sie auch auf einem Ziegel auffällt
	\colorlet { karolMarke }        { black!32 }
	\colorlet { karolWMarke }       { black!32 }
	\colorlet { karolWMarkeRand }   { black!60 }
	\colorlet { karolWZelleMarke }  { black!32 }
	% 2D: die Ziegelzelle bekommt nur einen Hauch Grau — die Stapelzahl
	% steht ohnehin darin und sagt schon alles.
	\colorlet { karolWZelleZiegel } { black!10 }
	\colorlet { karolQuader }       { black!22 }
	\colorlet { karolWZelleQuader } { black!22 }
	\colorlet { karolWHervor }      { black }
	% --- Figur --------------------------------------------------------
	% Gestaffelt, damit Mütze (Blickrichtung!), Haar, Hemd und Hose
	% auseinanderzuhalten sind; die Haut bleibt hell, gibt dem Kopf aber
	% noch eine Silhouette vor weißem Grund.
	\colorlet { karolAvatarKappe }  { black!70 }
	\colorlet { karolAvatarShirt }  { black!30 }
	\colorlet { karolAvatarHose }   { black!50 }
	\colorlet { karolAvatarHaut }   { black!10 }
	\colorlet { karolAvatarHaar }   { black!58 }
	\colorlet { karolEmmaShirt }    { black!22 }
	\colorlet { karolEmmaHaar }     { black!58 }
	\colorlet { karolEmmaBluete }   { black!35 }
	\colorlet { karolEmmaStiel }    { black!45 }
	% --- Blöcke -------------------------------------------------------
	\colorlet { karolBlockBefehl }    { white }
	% Die eigenen Anweisungen bekommen keinen Grauwert, sondern eine
	% Schraffur (siehe \__karol_brand_druck:nn): ein dunklerer Ton allein
	% war von \bbefehl zu schlecht zu unterscheiden, und beide Bloecke
	% haben dieselbe Form.  Der Grund darunter ist weiss, damit die
	% Pille mit dem Namen sich klar abhebt.
	\colorlet { karolBlockAnweisung } { white }
	\colorlet { karolBlockSchleife }  { black!10 }
	\colorlet { karolBlockWenn }      { black!10 }
	\colorlet { karolBlockBedingung } { black!16 }
	\colorlet { karolBlockKommentar } { black!16 }
	\colorlet { karolBlockHut }       { black!28 }
	% Eingabepillen bekommen einen Rand (siehe oben)
	\tikzset{ karolpillenrand/.style =
		{ draw = karolDruckKontur, line~width = \l__karol_bkw_dim } }
}
% Zu Beginn jeder Zeichnung aufzurufen (in deren Gruppe!).
\cs_new_protected:Npn \__karol_farbschema:
{
	\bool_if:NT \l__karol_druck_bool
	{
		\bool_if:NF \l__karol_druckda_bool
		{
			\bool_set_true:N \l__karol_druckda_bool
			\__karol_druckfarben:
		}
	}
}
% Eine beliebige Farbangabe in ihren Grauwert umsetzen (eigene
% Markenfarben: verschiedene Farben bleiben als verschiedene Grauwerte
% unterscheidbar).
\cs_new_protected:Npn \__karol_grau:n #1
	{ \colorlet { karolWMtmp } [ gray ] { #1 } }
\cs_generate_variant:Nn \__karol_grau:n { V }

% ===========================================================================
%  4. Karol-Welten im Schrägbild: Bausteine und karolwelt-Umgebung
% ===========================================================================
%  \begin{karolwelt}[breite=5, laenge=3, ansicht=3d]
%      \karol[x=1, y=2, richtung=osten]
%      \ziegel[x=2, y=2, anzahl=1, bisx=4]
%      \marke[x=5, y=1]
%      \quader[x=4, y=3, bisy=3]     (\wand = Synonym)
%  \end{karolwelt}
%
%  Koordinaten wie in Robot Karol Online: x = Spalte (1 .. breite, von
%  links), y = Zeile (1 .. laenge, von HINTEN); hoehe = Raumhöhe (nur
%  3D-Rahmen). In der 2D-Ansicht ist Zeile y=1 die oberste Zeile.
% ---------------------------------------------------------------------------
% Farben der Online-Oberfläche (gesampelt)
\definecolor{karolWZiegel}{RGB}{224,48,48}       % Ziegel in Zeilen-Welten
\definecolor{karolWZiegelDrei}{RGB}{150,8,8}     % Ziegelkörper 3D-Welt
\definecolor{karolWZiegelKante}{RGB}{255,0,0}    % Ziegel-Lagenkanten 3D
\definecolor{karolWFuge}{RGB}{153,20,40}         % Ziegelfugen (Zeilen-Welten)
\definecolor{karolWMarke}{RGB}{250,205,60}       % Marke 3D
\definecolor{karolWMarkeRand}{RGB}{214,143,0}    % Marke 3D, Rand
\definecolor{karolWGitter}{RGB}{45,45,215}       % Bodengitter
% Andreaskreuz über allem, was verschwinden soll (v3.9).  Deckend, aus
% ziegelWeg/markeWeg der Vorlage gesampelt.
\definecolor{karolWKreuz}{RGB}{160,160,160}
% Pfeil über allem, was entstehen soll (v3.9).  Die Vorlage zeichnet die
% ganze Einheit mit 60 % Deckkraft; gemessen wurde also der Wert NACH dem
% Mischen mit Weiß — RGB(214,214,214) bzw. RGB(102,102,102).  Hier stehen
% die zurückgerechneten Farben, die Deckkraft kommt beim Zeichnen dazu.
\definecolor{karolWDiff}{RGB}{255,0,0}   % 2D: Differenz zum Zielzustand
\definecolor{karolWPfeil}{RGB}{187,187,187}
\definecolor{karolWPfeilKante}{gray}{0}
% Hervorhebung: bewusst magenta statt rot — die Ziegelkanten sind reines
% Rot, ein roter Rahmen wäre auf einem Ziegelfeld nicht zu erkennen.
\definecolor{karolWHervor}{RGB}{226,0,122}       % hervorgehobene Felder
\definecolor{karolWZelleZiegel}{RGB}{255,205,205}% 2D: Ziegelzelle
\definecolor{karolWZelleMarke}{RGB}{210,212,39}  % 2D: Markenfläche
\definecolor{karolWZelleQuader}{RGB}{128,128,128}% 2D: Quaderzelle
% Avatar
\definecolor{karolAvatarKappe}{RGB}{198,32,44}
\definecolor{karolAvatarShirt}{RGB}{186,44,54}
\definecolor{karolAvatarHose}{RGB}{62,92,168}
\definecolor{karolAvatarHaut}{RGB}{244,204,158}
\definecolor{karolAvatarHaar}{RGB}{122,74,34}
% Zweite Figur „Emma": dieselbe Figur mit langem Haar und einer Blume.
% Die Farben sind aus der Paketpalette abgeleitet, nicht aus der
% Oberfläche übernommen — nachgebaut, nicht kopiert.
\definecolor{karolEmmaShirt}{RGB}{212,124,130}   % karolAvatarShirt aufgehellt
\definecolor{karolEmmaHaar}{RGB}{138,86,48}      % karolAvatarHaar aufgehellt
\definecolor{karolEmmaBluete}{RGB}{250,205,60}   % wie die Marke
\definecolor{karolEmmaStiel}{RGB}{60,150,70}

% Projektion (aus den Online-Screenshots vermessen), in Feldbreiten:
\fp_const:Nn \c__karol_wdx_fp { 0.47 }   % Tiefenversatz je Reihe, x
\fp_const:Nn \c__karol_wdy_fp { 0.69 }   % Tiefenversatz je Reihe, y
\fp_const:Nn \c__karol_wzh_fp { 0.46 }   % Höhe eines Ziegels
\fp_const:Nn \c__karol_wqh_fp { 0.80 }   % Höhe eines Quaders

% --- „soll entstehen" (v3.9), alle Maße in Feldbreiten -------------------
% Deckkraft der Geisterdarstellung: in der Vorlage 0.6 (aus dem Ziegelrot
% über Weiß zurückgerechnet, die Rechnung geht nur bei 0.6 auf).
\fp_const:Nn \c__karol_wneu_fp  { 0.6 }
% Geisterziegel: seine BOUNDINGBOX misst in ZielZiegelLegen.png 0.43
% Feldbreiten, in den Nachzeichnungen 0.99 Pfeilhöhen (= 0.51
% Feldbreiten); genommen ist die Mitte, 0.47.  Das ist NICHT der
% Maßstab — die Boundingbox eines Ziegels ist (1 + wdx) Feldbreiten
% breit, der Maßstab also 0.47 / 1.47.
\fp_const:Nn \c__karol_wmini_fp { 0.32 }
% Der Pfeil ist KEIN Polygon mit Kopf, sondern drei dicke Striche mit
% runden Enden: ein senkrechter Schaft und zwei Flügel, die schräg nach
% außen oben zeigen.  Gemessen an den Nachzeichnungen der Vorlage
% (referenz-karol-online/MehrereZiegelNachgezeichnet1.png, Pfeilhöhe
% 277 px), alle Maße als Vielfache der Pfeilhöhe:
%   Schaft   Breite 0.242, von y = 0.121 bis y = 0.879
%   Flügel   Breite 0.170, von (0, 0.065) nach (±0.323, 0.332)
% Der Ursprung ist die SPITZE des Pfeils; die Flügel treffen den Schaft
% unterhalb seiner Kappenmitte, dadurch entsteht die Spitze.
\fp_const:Nn \c__karol_wpfhoehe_fp   { 0.52 }  % Gesamthöhe in Feldbreiten
\fp_const:Nn \c__karol_wpfschaft_fp  { 0.242 } % Schaftbreite  (× Höhe)
\fp_const:Nn \c__karol_wpfsu_fp      { 0.121 } % Schaft unten  (× Höhe)
\fp_const:Nn \c__karol_wpfso_fp      { 0.879 } % Schaft oben   (× Höhe)
\fp_const:Nn \c__karol_wpffl_fp      { 0.170 } % Flügelbreite  (× Höhe)
\fp_const:Nn \c__karol_wpffj_fp      { 0.065 } % Flügelansatz  (× Höhe)
\fp_const:Nn \c__karol_wpffx_fp      { 0.323 } % Flügelspitze x (× Höhe)
\fp_const:Nn \c__karol_wpffy_fp      { 0.332 } % Flügelspitze y (× Höhe)
% Lage des Geisterziegels, von der PFEILSPITZE aus gemessen: seine
% vordere untere linke Ecke sitzt rechts davon und etwas darüber, sodass
% der Pfeil ihn nur halb verdeckt.  In den Nachzeichnungen 0.128 bzw.
% 0.072 Pfeilhöhen, in ZielZiegelLegen.png 0.16 bzw. 0.081.
\fp_const:Nn \c__karol_wminidx_fp { 0.073 }
\fp_const:Nn \c__karol_wminidy_fp { 0.040 }
% X-Fläche einer Marke, die gesetzt werden soll.  Zwei Bandbreiten:
% k an der Kreuzung, kt an den vier Spitzen — beide durch Anpassung an
% markeVorschau.png bestimmt (beste Überdeckung bei k=0.17, kt=0.14).
% Die Spitzen laufen in die Zellecken hinein, wie in der Vorlage.
\fp_const:Nn \c__karol_wxk_fp  { 0.17 }
\fp_const:Nn \c__karol_wxkt_fp { 0.14 }
% Im Druck wird die Geisterdarstellung strukturell übersetzt statt
% farblich: volle Deckkraft, weißer Körper, GESTRICHELTE Kontur.  Ein
% 60-%-Grau auf einem weißen Druckziegel wäre nicht zu sehen, und
% gestrichelt ist im Monochromen die lesbarere Aussage für „gibt es noch
% nicht".  Der Stil ist im Farbmodus leer und wird nur innerhalb der
% Geister-Gruppe umgesetzt — er ist dort wie jedes \tikzset lokal.
\tikzset{ karolgeistkante / .style = { } }

\tl_new:N   \l__karol_tmp_tl           % Streuvariable beider Renderer
\int_new:N  \l__karol_wbreite_int
\int_new:N  \l__karol_wlaenge_int
\int_new:N  \l__karol_whoehe_int
\tl_new:N   \l__karol_wansicht_tl
\tl_new:N   \l__karol_wskal_tl
\tl_new:N   \l__karol_wtitel_tl
\tl_new:N   \l__karol_whinweis_tl
% Start- und Zielzustand werden getrennt gehalten.  Eine Welt ohne
% start/ziel-Angaben füllt beide Props gleich — dann ist jede Zelle
% „unverändert" und die Ausgabe dieselbe wie vor v3.9.
\prop_new:N \l__karol_wziegel_prop     % "x,y" -> Stapelhöhe (Start)
\prop_new:N \l__karol_wzziegel_prop    % "x,y" -> Stapelhöhe (Ziel)
\prop_new:N \l__karol_wmarke_prop      % "x,y" -> Farbe      (Start)
\prop_new:N \l__karol_wzmarke_prop     % "x,y" -> Farbe      (Ziel)
\prop_new:N \l__karol_wquader_prop     % "x,y" -> 1  (kein Zielzustand:
                                       % Karol kann keine Quader setzen)
\bool_new:N \l__karol_wkarol_bool
\int_new:N  \l__karol_wkx_int
\int_new:N  \l__karol_wky_int
\tl_new:N   \l__karol_wkdir_tl         % o | w | s | n
\tl_new:N   \l__karol_wzustand_tl      % start | ziel | beides
\bool_new:N \l__karol_waufr_bool       % Aufräumauftrag?
\bool_new:N \l__karol_wrahmen_bool     % Raumrahmen zeichnen? (nur 3D)
\clist_new:N \l__karol_whervor_clist   % hervorgehobene Zellen, „x/y"


\keys_define:nn { karol / weltumg }
{
	breite   .int_set:N   = \l__karol_wbreite_int,
	breite   .initial:n   = { 5 },
	laenge   .int_set:N   = \l__karol_wlaenge_int,
	laenge   .initial:n   = { 5 },
	hoehe    .int_set:N   = \l__karol_whoehe_int,
	hoehe    .initial:n   = { 6 },
	ansicht  .choices:nn  = { 3d, 2d }
		{ \tl_set:Nx \l__karol_wansicht_tl { \l_keys_choice_tl } },
	ansicht  .initial:n   = { 3d },
	% Welcher Zustand soll gezeigt werden?  beides hebt die Unterschiede
	% zwischen Start und Ziel hervor; start und ziel zeigen je eine Welt
	% in der gewohnten Optik.
	zustand  .choices:nn  = { start, ziel, beides }
		{ \tl_set:Nx \l__karol_wzustand_tl { \l_keys_choice_tl } },
	zustand  .initial:n   = { beides },
	% Aufräumauftrag: die Zielwelt ist leer.  Kehrt nur die Vorgabe der
	% Objekte um (siehe \__karol_wobj_seiten:), nennt ein Objekt eine
	% Seite ausdrücklich, gilt weiter dessen Angabe.
	aufraeumen .bool_set:N = \l__karol_waufr_bool,
	aufraeumen .default:n  = true,
	aufraeumen .initial:n  = false,
	% Raumrahmen.  Für ein Legendenbildchen oder eine kleine Welt im
	% Fließtext ist er zu wuchtig; in der 2D-Ansicht gibt es ihn nicht.
	rahmen   .bool_set:N  = \l__karol_wrahmen_bool,
	rahmen   .default:n   = true,
	rahmen   .initial:n   = true,
	% Hervorhebung einzelner Zellen: Liste aus „x/y"-Paaren.
	hervorheben .clist_set:N = \l__karol_whervor_clist,
	hervorheben .initial:n   = { },
	skalierung .tl_set:N  = \l__karol_wskal_tl,
	skalierung .initial:n = { 1 },
	titel    .tl_set:N    = \l__karol_wtitel_tl,
	titel    .initial:n   = { },
	hinweis  .tl_set:N    = \l__karol_whinweis_tl,
	hinweis  .initial:n   = { },
}
% Dieselben Vorgaben noch einmal als Liste: die Umgebung setzt sie bei
% jedem Aufruf, denn .initial:n wirkt nur einmal beim Laden des Pakets.
\tl_const:Nn \c__karol_wvorgabe_tl
{
	breite = 5, laenge = 5, hoehe = 6, ansicht = 3d,
	zustand = beides, aufraeumen = false,
	rahmen = true, hervorheben = { },
	skalierung = 1, titel = { }, hinweis = { },
}

% --- Objekt-Schlüssel ------------------------------------------------------
\int_new:N \l__karol_wox_int
\int_new:N \l__karol_woy_int
\int_new:N \l__karol_wobisx_int
\int_new:N \l__karol_wobisy_int
\int_new:N \l__karol_woanz_int
\tl_new:N  \l__karol_wofarbe_tl
% Start- und Zielseite eines Objekts.  Die Schlüssel setzen nur diese
% Register; aufgelöst wird einmal am Ende des Objektbefehls
% (\__karol_wobj_seiten:).  Sonst hinge das Ergebnis von der
% Reihenfolge der Schlüssel ab: „anzahl=3, ziel" und „ziel, anzahl=3"
% müssen dasselbe bedeuten.
\int_new:N \l__karol_wosanz_int       % startanzahl, -1 = nicht gesetzt
\int_new:N \l__karol_wozanz_int       % zielanzahl,  -1 = nicht gesetzt
\bool_new:N \l__karol_wostart_bool    % „start"/„weg" genannt?
\bool_new:N \l__karol_woziel_bool     % „ziel"/„neu" genannt?
\int_new:N \l__karol_wos_int          % Ergebnis: Anzahl im Start
\int_new:N \l__karol_woz_int          % Ergebnis: Anzahl im Ziel

\keys_define:nn { karol / objekt }
{
	x      .int_set:N = \l__karol_wox_int,
	y      .int_set:N = \l__karol_woy_int,
	bisx   .int_set:N = \l__karol_wobisx_int,
	bisy   .int_set:N = \l__karol_wobisy_int,
	bisX   .meta:n    = { bisx = #1 },
	bisY   .meta:n    = { bisy = #1 },
	anzahl .int_set:N = \l__karol_woanz_int,
	farbe  .tl_set:N  = \l__karol_wofarbe_tl,
	% --- Start- und Zielzustand (v3.9) --------------------------------
	start  .bool_set:N = \l__karol_wostart_bool,
	start  .default:n  = true,
	ziel   .bool_set:N = \l__karol_woziel_bool,
	ziel   .default:n  = true,
	startanzahl .int_set:N = \l__karol_wosanz_int,
	zielanzahl  .int_set:N = \l__karol_wozanz_int,
	start-anzahl .meta:n = { startanzahl = #1 },
	ziel-anzahl  .meta:n = { zielanzahl  = #1 },
	% Handlungsnamen: so denkt man beim Schreiben einer Aufgabe.  „nur im
	% Ziel vorhanden" heißt „soll gelegt werden", „nur im Start" heißt
	% „soll weggeräumt werden".  hinlegen/aufheben und setzen/loeschen
	% sind die Befehlsnamen von Robot Karol selbst — sie stehen in
	% derselben Schlüsselmenge, gedacht sind sie aber je für ein Objekt
	% (Ziegel legt man hin, Marken setzt man).
	neu      .bool_set:N = \l__karol_woziel_bool,
	neu      .default:n  = true,
	hinlegen .bool_set:N = \l__karol_woziel_bool,
	hinlegen .default:n  = true,
	setzen   .bool_set:N = \l__karol_woziel_bool,
	setzen   .default:n  = true,
	weg      .bool_set:N = \l__karol_wostart_bool,
	weg      .default:n  = true,
	aufheben .bool_set:N = \l__karol_wostart_bool,
	aufheben .default:n  = true,
	loeschen .bool_set:N = \l__karol_wostart_bool,
	loeschen .default:n  = true,
	richtung .choice: ,
	richtung / osten  .code:n = { \tl_set:Nn \l__karol_wkdir_tl { o } },
	richtung / westen .code:n = { \tl_set:Nn \l__karol_wkdir_tl { w } },
	richtung / sueden .code:n = { \tl_set:Nn \l__karol_wkdir_tl { s } },
	richtung / norden .code:n = { \tl_set:Nn \l__karol_wkdir_tl { n } },
	richtung / rechts .meta:n = { richtung = osten },
	richtung / links  .meta:n = { richtung = westen },
	richtung / vorn   .meta:n = { richtung = sueden },
	richtung / vorne  .meta:n = { richtung = sueden },
	richtung / unten  .meta:n = { richtung = sueden },
	richtung / hinten .meta:n = { richtung = norden },
	richtung / oben   .meta:n = { richtung = norden },
}
\cs_new_protected:Npn \__karol_wobjekt_reset:
{
	\int_set:Nn \l__karol_wox_int    { 1 }
	\int_set:Nn \l__karol_woy_int    { 1 }
	\int_zero:N \l__karol_wobisx_int
	\int_zero:N \l__karol_wobisy_int
	\int_set:Nn \l__karol_woanz_int  { 1 }
	\tl_set:Nn  \l__karol_wofarbe_tl { karolWMarke }
	\int_set:Nn \l__karol_wosanz_int { -1 }
	\int_set:Nn \l__karol_wozanz_int { -1 }
	\bool_set_false:N \l__karol_wostart_bool
	\bool_set_false:N \l__karol_woziel_bool
}
% Start- und Zielanzahl eines Objekts bestimmen.  Genannt ist eine Seite,
% wenn ihr Schalter oder ihre Anzahl gesetzt wurde.  Wurde keine Seite
% genannt, gilt das Objekt in beiden Zuständen — außer die Welt ist ein
% Aufräumauftrag, dann nur im Start.
\cs_new_protected:Npn \__karol_wobj_seiten:
{
	\int_compare:nT { \l__karol_wosanz_int >= 0 }
		{ \bool_set_true:N \l__karol_wostart_bool }
	\int_compare:nT { \l__karol_wozanz_int >= 0 }
		{ \bool_set_true:N \l__karol_woziel_bool }
	\bool_lazy_or:nnTF
		{ \l__karol_wostart_bool } { \l__karol_woziel_bool }
	{
		\int_set:Nn \l__karol_wos_int
		{
			\bool_if:NTF \l__karol_wostart_bool
			{ \int_compare:nNnTF \l__karol_wosanz_int > { -1 }
				{ \l__karol_wosanz_int } { \l__karol_woanz_int } }
			{ 0 }
		}
		\int_set:Nn \l__karol_woz_int
		{
			\bool_if:NTF \l__karol_woziel_bool
			{ \int_compare:nNnTF \l__karol_wozanz_int > { -1 }
				{ \l__karol_wozanz_int } { \l__karol_woanz_int } }
			{ 0 }
		}
	}
	{
		\int_set_eq:NN \l__karol_wos_int \l__karol_woanz_int
		\bool_if:NTF \l__karol_waufr_bool
			{ \int_zero:N \l__karol_woz_int }
			{ \int_set_eq:NN \l__karol_woz_int \l__karol_woanz_int }
	}
}
% Bereich (x..bisx) x (y..bisy) auf ein Prop-Feld anwenden: #1 Prop, #2 Wert
\cs_new_protected:Npn \__karol_wbereich:Nn #1#2
{
	\int_compare:nT { \l__karol_wobisx_int < \l__karol_wox_int }
		{ \int_set_eq:NN \l__karol_wobisx_int \l__karol_wox_int }
	\int_compare:nT { \l__karol_wobisy_int < \l__karol_woy_int }
		{ \int_set_eq:NN \l__karol_wobisy_int \l__karol_woy_int }
	\int_step_inline:nnn { \l__karol_wox_int } { \l__karol_wobisx_int }
	{
		\int_step_inline:nnn { \l__karol_woy_int } { \l__karol_wobisy_int }
		{ \prop_put:Nnx #1 { ##1 , ####1 } {#2} }
	}
}

% Die Objektkommandos heißen \karol, \ziegel, \marke, \quader und \wand.
% Das sind gewöhnliche deutsche Wörter, die ein Dokument selbst belegt
% haben kann — global definiert würde das Paket dort nicht mehr laden.
% Sie werden deshalb unter privaten Namen angelegt und erst in der
% Umgebung karolwelt sichtbar gemacht (\__karol_wobjekte:).  Da die
% Umgebung eine Gruppe bildet, gilt eine etwaige eigene Bedeutung
% außerhalb unverändert weiter.
\NewDocumentCommand { \__karol_wobj_karol:w } { O{} }
{
	\__karol_wobjekt_reset:
	\keys_set:nn { karol / objekt } { richtung = sueden, #1 }
	\bool_set_true:N \l__karol_wkarol_bool
	\int_set_eq:NN \l__karol_wkx_int \l__karol_wox_int
	\int_set_eq:NN \l__karol_wky_int \l__karol_woy_int
}
\NewDocumentCommand { \__karol_wobj_ziegel:w } { O{} }
{
	\__karol_wobjekt_reset:
	\keys_set:nn { karol / objekt } { #1 }
	\__karol_wobj_seiten:
	% Ein Stapel der Höhe 0 darf KEINEN Prop-Eintrag bekommen: an mehreren
	% Stellen unterscheidet \prop_if_in:NnTF „Zelle belegt" von „leer".
	\int_compare:nT { \l__karol_wos_int > 0 }
		{ \__karol_wbereich:Nn \l__karol_wziegel_prop
			{ \int_use:N \l__karol_wos_int } }
	\int_compare:nT { \l__karol_woz_int > 0 }
		{ \__karol_wbereich:Nn \l__karol_wzziegel_prop
			{ \int_use:N \l__karol_woz_int } }
}
\NewDocumentCommand { \__karol_wobj_marke:w } { O{} }
{
	\__karol_wobjekt_reset:
	\keys_set:nn { karol / objekt } { #1 }
	\__karol_wobj_seiten:
	% Eine Marke hat keine Anzahl: jeder Wert > 0 heißt „liegt dort".
	\int_compare:nT { \l__karol_wos_int > 0 }
	{
		\exp_args:NNx \__karol_wbereich:Nn \l__karol_wmarke_prop
			{ \tl_use:N \l__karol_wofarbe_tl }
	}
	\int_compare:nT { \l__karol_woz_int > 0 }
	{
		\exp_args:NNx \__karol_wbereich:Nn \l__karol_wzmarke_prop
			{ \tl_use:N \l__karol_wofarbe_tl }
	}
}
\NewDocumentCommand { \__karol_wobj_quader:w } { O{} }
{
	\__karol_wobjekt_reset:
	\keys_set:nn { karol / objekt } { #1 }
	\__karol_wbereich:Nn \l__karol_wquader_prop { 1 }
}
\cs_new_protected:Npn \__karol_wobjekte:
{
	\cs_set_eq:NN \karol  \__karol_wobj_karol:w
	\cs_set_eq:NN \ziegel \__karol_wobj_ziegel:w
	\cs_set_eq:NN \marke  \__karol_wobj_marke:w
	\cs_set_eq:NN \quader \__karol_wobj_quader:w
	\cs_set_eq:NN \wand   \__karol_wobj_quader:w
}

% --- Zustand einer Zelle ---------------------------------------------------
% Aus Start- und Zielprop wird je Zelle abgeleitet, was gezeichnet werden
% muss.  Alle drei Ansichten laufen durch denselben Code; in start und
% ziel gibt es nur eine Welt, dann ist alles „gleich" und die Ausgabe ist
% Pixel für Pixel die von v3.8.
%
%   ngleich = min(start, ziel)          bleibt liegen
%   nneu    = max(0, ziel  - start)     soll entstehen   (oben auf dem Stapel)
%   nweg    = max(0, start - ziel)      soll verschwinden (oben auf dem Stapel)
%
% Die Marke liegt auf den Ziegeln des STARTS — außer sie ist neu, dann auf
% max(start, ziel).  Genau so macht es die Vorlage: Ziegel, die unter einer
% bleibenden Marke dazukommen, wachsen über ihr auf; Ziegel, die unter ihr
% verschwinden, lassen sie oben sitzen.
\tl_new:N   \l__karol_wtmp_tl          % eigene Streuvariable des Status
\int_new:N  \l__karol_wsz_int          % Ziegel im Start
\int_new:N  \l__karol_wzz_int          % Ziegel im Ziel
\int_new:N  \l__karol_wngleich_int
\int_new:N  \l__karol_wnneu_int
\int_new:N  \l__karol_wnweg_int
\int_new:N  \l__karol_wdiff_int        % 2D: ziel - start
\bool_new:N \l__karol_wms_bool         % Marke im Start?
\bool_new:N \l__karol_wmz_bool         % Marke im Ziel?
\tl_new:N   \l__karol_wmsfarbe_tl
\tl_new:N   \l__karol_wmzfarbe_tl
\tl_new:N   \l__karol_wmstatus_tl      % keine | gleich | neu | weg
\tl_new:N   \l__karol_wmfarbe_tl
\int_new:N  \l__karol_wmhoehe_int      % Lagen, auf denen die Marke liegt
% Der Stapel der GEZEIGTEN Welt (start bzw. ziel; bei „beides" der des
% Starts).  Die 2D-Ansicht färbt und beziffert danach — sie zeigt den
% Ausgangszustand und daneben die Differenz, nicht den Schnitt beider
% Welten.  Auf derselben Höhe steht auch Karol.
\int_new:N  \l__karol_wbasis_int

% Stapelhöhe einer Zelle aus einem Prop holen (fehlender Eintrag = 0)
\cs_new_protected:Npn \__karol_wanzahl:NNnn #1#2#3#4
{
	\prop_get:NnNTF #1 { #3 , #4 } \l__karol_wtmp_tl
		{ \int_set:Nn #2 { \l__karol_wtmp_tl } }
		{ \int_zero:N #2 }
}
\cs_new_protected:Npn \__karol_wstatus:nn #1#2
{
	\__karol_wanzahl:NNnn \l__karol_wziegel_prop  \l__karol_wsz_int {#1}{#2}
	\__karol_wanzahl:NNnn \l__karol_wzziegel_prop \l__karol_wzz_int {#1}{#2}
	\bool_set_false:N \l__karol_wms_bool
	\bool_set_false:N \l__karol_wmz_bool
	\prop_get:NnNT \l__karol_wmarke_prop  { #1 , #2 } \l__karol_wmsfarbe_tl
		{ \bool_set_true:N \l__karol_wms_bool }
	\prop_get:NnNT \l__karol_wzmarke_prop { #1 , #2 } \l__karol_wmzfarbe_tl
		{ \bool_set_true:N \l__karol_wmz_bool }
	\str_case:VnF \l__karol_wzustand_tl
	{
		{ start } { \__karol_wstatus_eine:NNN \l__karol_wsz_int
			\l__karol_wms_bool \l__karol_wmsfarbe_tl }
		{ ziel }  { \__karol_wstatus_eine:NNN \l__karol_wzz_int
			\l__karol_wmz_bool \l__karol_wmzfarbe_tl }
	}
	{ \__karol_wstatus_beides: }
}
% Eine einzelne Welt: alles „gleich", nichts kommt, nichts geht.
\cs_new_protected:Npn \__karol_wstatus_eine:NNN #1#2#3
{
	\int_set_eq:NN \l__karol_wngleich_int #1
	\int_zero:N \l__karol_wnneu_int
	\int_zero:N \l__karol_wnweg_int
	\int_set_eq:NN \l__karol_wmhoehe_int #1
	\int_set_eq:NN \l__karol_wbasis_int    #1
	\bool_if:NTF #2
		{ \tl_set:Nn \l__karol_wmstatus_tl { gleich }
		  \tl_set_eq:NN \l__karol_wmfarbe_tl #3 }
		{ \tl_set:Nn \l__karol_wmstatus_tl { keine } }
}
\cs_new_protected:Npn \__karol_wstatus_beides:
{
	\int_set:Nn \l__karol_wngleich_int
		{ \int_min:nn { \l__karol_wsz_int } { \l__karol_wzz_int } }
	\int_set:Nn \l__karol_wnneu_int
		{ \int_max:nn { 0 } { \l__karol_wzz_int - \l__karol_wsz_int } }
	\int_set:Nn \l__karol_wnweg_int
		{ \int_max:nn { 0 } { \l__karol_wsz_int - \l__karol_wzz_int } }
	% Karol steht in der Startwelt
	\int_set_eq:NN \l__karol_wbasis_int \l__karol_wsz_int
	\int_set_eq:NN \l__karol_wmhoehe_int \l__karol_wsz_int
	\bool_lazy_and:nnTF { \l__karol_wms_bool } { \l__karol_wmz_bool }
		{ \tl_set:Nn \l__karol_wmstatus_tl { gleich }
		  \tl_set_eq:NN \l__karol_wmfarbe_tl \l__karol_wmsfarbe_tl }
	{
		\bool_lazy_and:nnTF { \l__karol_wms_bool }
			{ ! \l__karol_wmz_bool }
			{ \tl_set:Nn \l__karol_wmstatus_tl { weg }
			  \tl_set_eq:NN \l__karol_wmfarbe_tl \l__karol_wmsfarbe_tl }
		{
			\bool_lazy_and:nnTF { ! \l__karol_wms_bool }
				{ \l__karol_wmz_bool }
			{
				% Die Höhe bleibt der Startstapel.  Karol Online zeigt das
				% Zeichen für eine zu setzende Marke IMMER unten auf der
				% Zellfläche — auch wenn die Marke am Ende oben auf den
				% neuen Ziegeln läge.  Sonst schwebte es über den
				% Geisterziegeln in der Luft.
				\tl_set:Nn \l__karol_wmstatus_tl { neu }
				\tl_set_eq:NN \l__karol_wmfarbe_tl \l__karol_wmzfarbe_tl
			}
			{ \tl_set:Nn \l__karol_wmstatus_tl { keine } }
		}
	}
}

% --- 2D-Ansicht (Draufsicht wie in Karol Online) ---------------------------
\cs_new_protected:Npn \__karol_wrender_iid:
{
	\begin{tikzpicture}
		[ x=\dim_use:N \g__karol_feld_dim ,
		  y=-\dim_use:N \g__karol_feld_dim ]
		% Zellfüllungen
		\int_step_inline:nn { \l__karol_wbreite_int }
		{
			\int_step_inline:nn { \l__karol_wlaenge_int }
			{
				\__karol_wzelle_iid:nn { ##1 } { ####1 }
			}
		}
		% Gitter (explizit: „grid" nutzt xstep/ystep als Canvas-Maß und
		% versagt bei negativem y-Einheitsvektor)
		\int_step_inline:nnn { 0 } { \l__karol_wbreite_int }
		{
			\draw [ karolWGitter, line~width=.55pt ]
				( ##1 , 0 ) -- ( ##1 , \int_use:N \l__karol_wlaenge_int );
		}
		\int_step_inline:nnn { 0 } { \l__karol_wlaenge_int }
		{
			\draw [ karolWGitter, line~width=.55pt ]
				( 0 , ##1 ) -- ( \int_use:N \l__karol_wbreite_int , ##1 );
		}
		% Hervorhebungen über die Zellfarben, aber unter Karol
		\__karol_whervor_alle:N \__karol_whervor_iid:nn
		% Karol
		\bool_if:NT \l__karol_wkarol_bool
		{
			\begin{scope}
				[ shift={ ( { \l__karol_wkx_int - 0.5 },
				            { \l__karol_wky_int - 0.5 } ) },
				  rotate=\str_case:Vn \l__karol_wkdir_tl
					{ { o } { 0 } { n } { 90 } { w } { 180 } { s } { -90 } } ]
				\fill [ black ]
					(-0.34, -0.26) -- (0.38, 0) -- (-0.34, 0.26)
					-- (-0.18, 0) -- cycle;
			\end{scope}
		}
	\end{tikzpicture}
}
\cs_new_protected:Npn \__karol_wzelle_iid:nn #1#2
{
	\__karol_wstatus:nn { #1 } { #2 }
	\prop_if_in:NnT \l__karol_wquader_prop { #1 , #2 }
	{
		\fill [ karolWZelleQuader ]
			( {#1 - 1}, {#2 - 1} ) rectangle ( {#1}, {#2} );
	}
	% Der Ziegelstapel färbt die ganze Zelle — auch unter einer Marke, damit
	% „Marke auf Ziegel“ erkennbar bleibt.
	\int_compare:nT { \l__karol_wbasis_int > 0 }
	{
		\fill [ karolWZelleZiegel ]
			( {#1 - 1}, {#2 - 1} ) rectangle ( {#1}, {#2} );
	}
	% Marke: einfarbige Fläche (kein Kreuz), kleiner als das Feld, sodass der
	% Ziegel ringsum sichtbar bleibt.  Eine Marke, die erst gesetzt werden
	% soll, hat keine volle Fläche, sondern nur die X-Form.
	\str_if_eq:VnF \l__karol_wmstatus_tl { keine }
	{
		\tl_set_eq:NN \l__karol_tmp_tl \l__karol_wmfarbe_tl
		\str_if_eq:VnT \l__karol_tmp_tl { karolWMarke }
			{ \tl_set:Nn \l__karol_tmp_tl { karolWZelleMarke } }
		\bool_if:NT \l__karol_druck_bool
		{
			\__karol_grau:V \l__karol_tmp_tl
			\tl_set:Nn \l__karol_tmp_tl { karolWMtmp }
		}
		\str_if_eq:VnTF \l__karol_wmstatus_tl { neu }
		{
			\fp_set:Nn \l__karol_wxox_fp { #1 - 0.82 }
			\fp_set:Nn \l__karol_wxoy_fp { #2 - 0.82 }
			\fp_set:Nn \l__karol_wxs_fp  { 0.64 }
			% Im Druck wie im Schrägbild: weiße Fläche, gestrichelte
			% Kontur — „gibt es noch nicht" und kaum Toner.
			\bool_if:NTF \l__karol_druck_bool
			{
				\fill [ white ] \__karol_wxflaeche_iid: ;
				\draw [ karolDruckKontur, line~width=.5pt,
				        dash~pattern = on~1.2pt~off~1pt ]
					\__karol_wxflaeche_iid: ;
			}
			{ \fill [ \l__karol_tmp_tl ] \__karol_wxflaeche_iid: ; }
		}
		{
			\fill [ \l__karol_tmp_tl ]
				( {#1 - 0.82}, {#2 - 0.82} )
				rectangle ( {#1 - 0.18}, {#2 - 0.18} );
			% Soll die Marke gelöscht werden, kommen die Diagonalen darüber.
			\str_if_eq:VnT \l__karol_wmstatus_tl { weg }
			{
				\draw [ karolWKreuz, opacity=0.5, line~width=1pt ]
					( {#1 - 0.82}, {#2 - 0.82} ) -- ( {#1 - 0.18}, {#2 - 0.18} )
					( {#1 - 0.18}, {#2 - 0.82} ) -- ( {#1 - 0.82}, {#2 - 0.18} );
			}
		}
	}
	% Stapelzahl des gezeigten Zustands, mittig.  Steht nach der X-Form,
	% damit sie darüber liegt (wie in der Vorlage).
	\int_compare:nT { \l__karol_wbasis_int > 0 }
	{
		\node at ( {#1 - 0.5}, {#2 - 0.5} )
			{ \normalfont \__karol_textfont: \small
			  \int_use:N \l__karol_wbasis_int };
	}
	% Differenz zum Zielzustand, rot, oben rechts in der Zelle.  In den
	% Ansichten start und ziel sind nneu und nweg null — dort entfällt sie.
	\int_set:Nn \l__karol_wdiff_int
		{ \l__karol_wnneu_int - \l__karol_wnweg_int }
	\int_compare:nF { \l__karol_wdiff_int = 0 }
	{
		% Farbe vorher in ein tl-Register: ein Test im Schlüsselwert
		% sprengt xcolor (siehe Stolpersteine).
		\bool_if:NTF \l__karol_druck_bool
			{ \tl_set:Nn \l__karol_tmp_tl { karolDruckKontur } }
			{ \tl_set:Nn \l__karol_tmp_tl { karolWDiff } }
		\node [ anchor=north~east, inner~sep=0pt, text=\l__karol_tmp_tl ]
			at ( {#1 - 0.05}, {#2 - 0.95} )
			{ \normalfont \__karol_textfont: \scriptsize
			  \bool_if:NT \l__karol_druck_bool { \bfseries }
			  \int_compare:nT { \l__karol_wdiff_int > 0 } { + }
			  \int_use:N \l__karol_wdiff_int };
	}
}

% --- 3D-Ansicht (Schrägbild wie in Karol Online) ---------------------------
% Bildkoordinaten einer Bodenecke: x-Spalte #1 (0-basiert), Tiefe #2
% (0 = vorderste Reihe), Höhe #3 (in Ziegellagen ausgedrückt als fp-Wert).
% Reihe y (1 = hinten) hat Tiefe  laenge - y.
\cs_new:Npn \__karol_wpx:nn #1#2
	{ \fp_eval:n { (#1) + \c__karol_wdx_fp * (#2) } }
\cs_new:Npn \__karol_wpy:nnn #1#2#3
	{ \fp_eval:n { \c__karol_wdy_fp * (#2) + \c__karol_wzh_fp * (#3) } }

\cs_new_protected:Npn \__karol_wrender_iiid:
{
	\begin{tikzpicture}
		[ x=\dim_use:N \g__karol_feld_dim ,
		  y=\dim_use:N \g__karol_feld_dim ]
		\__karol_wraum:
		% Hervorhebungen vor den Objekten: die Objekte sollen darauf stehen
		\__karol_whervor_alle:N \__karol_whervor_iiid:nn
		% Objekte, Reihe für Reihe von hinten (y=1) nach vorn
		\int_step_inline:nn { \l__karol_wlaenge_int }
		{
			\int_step_inline:nn { \l__karol_wbreite_int }
			{
				\__karol_wzelle_iiid:nn { ####1 } { ##1 }
			}
		}
	\end{tikzpicture}
}

% Raum: Bodengitter, Wandlinien, oberer Rahmen.
% In zwei Teile zerlegt, weil \karolzeile das Gitter immer, den Rahmen
% aber nur auf Wunsch (Schlüssel rahmen) zeichnet.
\cs_new_protected:Npn \__karol_wraum:
{
	\__karol_wboden_gitter:
	\bool_if:NT \l__karol_wrahmen_bool { \__karol_wrahmen: }
}
% Hervorhebung einer Zelle (#1 = x ab 1, #2 = y ab 1, hinten).  Wie in den
% Zeilen-Welten liegt der Rahmen 0.06 Feldbreiten AUSSERHALB der Zelle:
% sonst verdeckt ihn jeder Ziegelstapel, der die Zelle randlos ausfüllt.
\cs_new_protected:Npn \__karol_whervor_iiid:nn #1#2
{
	\draw [ karolWHervor, line~width=1.2pt, line~join=round ]
		( \__karol_wpx:nn { #1 - 1.06 } { \__karol_wtiefe:n {#2} - 0.06 } ,
		  \__karol_wpy:nnn { 0 } { \__karol_wtiefe:n {#2} - 0.06 } { 0 } )
		-- ( \__karol_wpx:nn { #1 + 0.06 } { \__karol_wtiefe:n {#2} - 0.06 } ,
		     \__karol_wpy:nnn { 0 } { \__karol_wtiefe:n {#2} - 0.06 } { 0 } )
		-- ( \__karol_wpx:nn { #1 + 0.06 } { \__karol_wtiefe:n {#2} + 1.06 } ,
		     \__karol_wpy:nnn { 0 } { \__karol_wtiefe:n {#2} + 1.06 } { 0 } )
		-- ( \__karol_wpx:nn { #1 - 1.06 } { \__karol_wtiefe:n {#2} + 1.06 } ,
		     \__karol_wpy:nnn { 0 } { \__karol_wtiefe:n {#2} + 1.06 } { 0 } )
		-- cycle;
}
\cs_new_protected:Npn \__karol_whervor_iid:nn #1#2
{
	\draw [ karolWHervor, line~width=1.2pt, line~join=round ]
		( {#1 - 1.06}, {#2 - 1.06} ) rectangle ( {#1 + 0.06}, {#2 + 0.06} );
}
% Ein „x/y"-Paar aufteilen und zeichnen
\cs_new_protected:Npn \__karol_whervor_paar:Nw #1#2/#3 \q_stop { #1 {#2} {#3} }
\cs_new_protected:Npn \__karol_whervor_alle:N #1
{
	\clist_map_inline:Nn \l__karol_whervor_clist
		{ \__karol_whervor_paar:Nw #1 ##1 \q_stop }
}

% Bodengitter (blau, in beide Richtungen)
\cs_new_protected:Npn \__karol_wboden_gitter:
{
	\int_step_inline:nnn { 0 } { \l__karol_wbreite_int }
	{	% Tiefenlinien
		\draw [ karolWGitter, line~width=.5pt ]
			( \__karol_wpx:nn { ##1 } { 0 },
			  \__karol_wpy:nnn { ##1 } { 0 } { 0 } )
			-- ( \__karol_wpx:nn { ##1 } { \l__karol_wlaenge_int },
			     \__karol_wpy:nnn { ##1 } { \l__karol_wlaenge_int } { 0 } );
	}
	\int_step_inline:nnn { 0 } { \l__karol_wlaenge_int }
	{	% Querlinien
		\draw [ karolWGitter, line~width=.5pt ]
			( \__karol_wpx:nn { 0 } { ##1 },
			  \__karol_wpy:nnn { 0 } { ##1 } { 0 } )
			-- ( \__karol_wpx:nn { \l__karol_wbreite_int } { ##1 },
			     \__karol_wpy:nnn { \l__karol_wbreite_int } { ##1 } { 0 } );
	}
}

% Raumrahmen: Rückwand, linke Wand, oberer Rahmen (Raumhöhe = hoehe Lagen)
\cs_new_protected:Npn \__karol_wrahmen:
{
	% Raumhöhe in Bildeinheiten
	\fp_set:Nn \l__karol_wtmp_fp
		{ \c__karol_wzh_fp * \l__karol_whoehe_int }
	% Rückwand: senkrechte gestrichelte Linien an jeder Gitterlinie
	\int_step_inline:nnn { 0 } { \l__karol_wbreite_int }
	{
		\draw [ black!55, line~width=.4pt, dashed ]
			( \__karol_wpx:nn { ##1 } { \l__karol_wlaenge_int },
			  \__karol_wpy:nnn { ##1 } { \l__karol_wlaenge_int } { 0 } )
			-- ++ ( 0, \fp_use:N \l__karol_wtmp_fp );
	}
	% Linke Wand
	\int_step_inline:nnn { 0 } { \l__karol_wlaenge_int }
	{
		\draw [ karolWGitter!70!white, line~width=.4pt, dashed ]
			( \__karol_wpx:nn { 0 } { ##1 },
			  \__karol_wpy:nnn { 0 } { ##1 } { 0 } )
			-- ++ ( 0, \fp_use:N \l__karol_wtmp_fp );
	}
	% Oberer Rahmen (strichpunktiert): hinten und links
	\draw [ black!70, line~width=.5pt, dash~pattern=on~4pt~off~2pt~on~.6pt~off~2pt ]
		( \__karol_wpx:nn { 0 } { 0 },
		  \fp_eval:n { \l__karol_wtmp_fp } )
		-- ( \__karol_wpx:nn { 0 } { \l__karol_wlaenge_int },
		     \fp_eval:n { \c__karol_wdy_fp * \l__karol_wlaenge_int
		                  + \l__karol_wtmp_fp } )
		-- ( \__karol_wpx:nn { \l__karol_wbreite_int }
		         { \l__karol_wlaenge_int },
		     \fp_eval:n { \c__karol_wdy_fp * \l__karol_wlaenge_int
		                  + \l__karol_wtmp_fp } );
}

% Eine Zelle in 3D: Quader / Ziegelstapel / Marke / Karol
\fp_new:N \l__karol_wtmp_fp
\fp_new:N \l__karol_wz_fp
\cs_new_protected:Npn \__karol_wzelle_iiid:nn #1#2   % #1 = x, #2 = y
{
	\__karol_wstatus:nn { #1 } { #2 }
	\fp_zero:N \l__karol_wz_fp   % Oberkante des Zellinhalts, in Lagen
	\prop_if_in:NnT \l__karol_wquader_prop { #1 , #2 }
		{ \__karol_wquader_iiid:nnn { #1 } { #2 } { 0 } }
	\int_step_inline:nn { \l__karol_wngleich_int }
		{ \__karol_wziegel_iiid:nnn { #1 } { #2 } { ##1 - 1 } }
	% Sitzt die Marke auf den bleibenden Lagen, wird sie JETZT gesetzt —
	% vor allem, was darüber noch entsteht oder verschwindet.  Sonst läge
	% sie über einem Geisterziegel, der doch auf ihr aufwachsen soll.
	\int_compare:nT { \l__karol_wmhoehe_int <= \l__karol_wngleich_int }
		{ \__karol_wmarke_zeichnen:nn { #1 } { #2 } }
	% Lagen, die verschwinden sollen, liegen oben auf dem Stapel.  Die
	% Deckfläche bekommt ihr Kreuz nur auf der obersten Lage.
	\int_step_inline:nn { \l__karol_wnweg_int }
	{
		\__karol_wziegel_weg_iiid:nnnn { #1 } { #2 }
			{ \l__karol_wngleich_int + ##1 - 1 }
			{ \int_compare:nNnTF { ##1 } = { \l__karol_wnweg_int } { 1 } { 0 } }
	}
	% Lagen, die entstehen sollen, wachsen ebenfalls oben auf.  nneu und
	% nweg schließen einander aus, ein Übereinander kann es nicht geben.
	% Die Geistereinheiten stapeln sich im Abstand ihrer EIGENEN Höhe,
	% nicht im Raster der Ziegellagen: in der Vorlage berühren sich Pfeil
	% und Pfeil genau, sie überschneiden sich nicht.  Die Lage darf
	% deshalb gebrochen sein.
	\int_step_inline:nn { \l__karol_wnneu_int }
	{
		\__karol_wziegel_neu_iiid:nnn { #1 } { #2 }
		{
			\fp_eval:n { \l__karol_wngleich_int + ( ##1 - 1 )
			             * \c__karol_wpfhoehe_fp / \c__karol_wzh_fp }
		}
	}
	\int_compare:nT { \l__karol_wmhoehe_int > \l__karol_wngleich_int }
		{ \__karol_wmarke_zeichnen:nn { #1 } { #2 } }
	\fp_set:Nn \l__karol_wz_fp { \l__karol_wbasis_int }
	\bool_if:NT \l__karol_wkarol_bool
	{
		\int_compare:nT { \l__karol_wkx_int = #1 }
		{
			\int_compare:nT { \l__karol_wky_int = #2 }
				{ \__karol_wavatar_iiid:nn { #1 } { #2 } }
		}
	}
}

% Marke auf ihrer Stapelhöhe zeichnen (Zustand, Farbe und Höhe stehen im
% Status).  „keine" zeichnet nichts — der Aufrufer prüft nur die Höhe.
\cs_new_protected:Npn \__karol_wmarke_zeichnen:nn #1#2
{
	\fp_set:Nn \l__karol_wz_fp { \l__karol_wmhoehe_int }
	\str_case:Vn \l__karol_wmstatus_tl
	{
		{ gleich } { \__karol_wmarke_setzen:nn { #1 } { #2 } }
		{ weg }    { \__karol_wmarke_setzen:nn { #1 } { #2 }
		             \__karol_wmarke_kreuz_iiid:nn { #1 } { #2 } }
		{ neu }
		{
			\exp_args:Nnnx \__karol_wmarke_neu_iiid:nnn { #1 } { #2 }
				{ \tl_use:N \l__karol_wmfarbe_tl }
		}
	}
}
% Volle Platte
\cs_new_protected:Npn \__karol_wmarke_setzen:nn #1#2
{
	\exp_args:Nnnx \__karol_wmarke_iiid:nnn { #1 } { #2 }
		{ \tl_use:N \l__karol_wmfarbe_tl }
}

% Hilfsgrößen einer Zelle (#1 x, #2 y): Tiefe t = laenge - y
\cs_new:Npn \__karol_wtiefe:n #1
	{ \int_eval:n { \l__karol_wlaenge_int - (#1) } }

% Ziegel: Würfelsegment auf Lage #3 (0-basiert)
\cs_new_protected:Npn \__karol_wziegel_iiid:nnn #1#2#3
{
	% Wie Karol Online: flacher dunkelroter Körper, jede Lage auf allen
	% Sichtflächen leuchtend rot umrandet (Stapelhöhe seitlich ablesbar)
	\__karol_wkubus:nnnnnnnn { #1 } { #2 } { #3 } { 1 }
		{ karolWZiegelDrei } { karolWZiegelDrei } { karolWZiegelDrei }
		{ karolWZiegelKante }
}

% Quader (grau, fester Höhenwert), ab Lage #3
\cs_new_protected:Npn \__karol_wquader_iiid:nnn #1#2#3
{
	\group_begin:
	\fp_set:Nn \l__karol_wtmp_fp
		{ \c__karol_wqh_fp / \c__karol_wzh_fp }
	\__karol_wkubus:nnnnnnnn { #1 } { #2 } { #3 }
		{ \fp_use:N \l__karol_wtmp_fp }
		{ karolWZelleQuader } { karolWZelleQuader!88!black }
		{ karolWZelleQuader!94!white } { black!75 }
	\group_end:
}

% Allgemeiner Kubus: Zelle (#1,#2), ab Lage #3, Höhe #4 Lagen,
% Farben: #5 Front, #6 Seite, #7 Deckel, #8 Kanten
\cs_new_protected:Npn \__karol_wkubus:nnnnnnnn #1#2#3#4#5#6#7#8
{
	\begin{scope}
		[ shift={ ( { \__karol_wpx:nn { #1 - 1 } { \__karol_wtiefe:n {#2} } },
		            { \__karol_wpy:nnn { 0 } { \__karol_wtiefe:n {#2} }
		                                { #3 } } ) } ]
		% rechte Seitenfläche
		\fill [ #6 ]
			( 1, 0 )
			-- ( { 1 + \fp_use:N \c__karol_wdx_fp },
			     { \fp_use:N \c__karol_wdy_fp } )
			-- ( { 1 + \fp_use:N \c__karol_wdx_fp },
			     { \fp_use:N \c__karol_wdy_fp
			       + (#4) * \fp_use:N \c__karol_wzh_fp } )
			-- ( 1, { (#4) * \fp_use:N \c__karol_wzh_fp } )
			-- cycle;
		% Deckfläche
		\fill [ #7 ]
			( 0, { (#4) * \fp_use:N \c__karol_wzh_fp } )
			-- ( 1, { (#4) * \fp_use:N \c__karol_wzh_fp } )
			-- ( { 1 + \fp_use:N \c__karol_wdx_fp },
			     { \fp_use:N \c__karol_wdy_fp
			       + (#4) * \fp_use:N \c__karol_wzh_fp } )
			-- ( { \fp_use:N \c__karol_wdx_fp },
			     { \fp_use:N \c__karol_wdy_fp
			       + (#4) * \fp_use:N \c__karol_wzh_fp } )
			-- cycle;
		% Frontfläche
		\fill [ #5 ]
			( 0, 0 ) rectangle ( 1, { (#4) * \fp_use:N \c__karol_wzh_fp } );
		% Kanten aller Sichtflächen
		\draw [ #8, line~width=.5pt, line~join=round, karolgeistkante ]
			( 0, 0 ) rectangle ( 1, { (#4) * \fp_use:N \c__karol_wzh_fp } );
		\draw [ #8, line~width=.5pt, line~join=round, karolgeistkante ]
			( 1, 0 )
			-- ( { 1 + \fp_use:N \c__karol_wdx_fp },
			     { \fp_use:N \c__karol_wdy_fp } )
			-- ( { 1 + \fp_use:N \c__karol_wdx_fp },
			     { \fp_use:N \c__karol_wdy_fp
			       + (#4) * \fp_use:N \c__karol_wzh_fp } )
			-- ( 1, { (#4) * \fp_use:N \c__karol_wzh_fp } );
		\draw [ #8, line~width=.5pt, line~join=round, karolgeistkante ]
			( 0, { (#4) * \fp_use:N \c__karol_wzh_fp } )
			-- ( { \fp_use:N \c__karol_wdx_fp },
			     { \fp_use:N \c__karol_wdy_fp
			       + (#4) * \fp_use:N \c__karol_wzh_fp } )
			-- ( { 1 + \fp_use:N \c__karol_wdx_fp },
			     { \fp_use:N \c__karol_wdy_fp
			       + (#4) * \fp_use:N \c__karol_wzh_fp } );
	\end{scope}
}

% Eckpunkte einer EINZELNEN Lage im lokalen System eines Kubus, als
% expandierbare Helfer, damit die Pfade unten lesbar bleiben:
% vu = vorn unten rechts, ho = hinten oben rechts, hl = hinten oben links.
% Die Höhe kommt aus \c__karol_wzh_fp — kein zweites Mal hingeschrieben,
% sonst liefe sie bei einer Änderung der Projektion still auseinander.
\cs_new:Npn \__karol_wlage: { \fp_use:N \c__karol_wzh_fp }
\cs_new:Npn \__karol_wecke_vu:
	{ ( { 1 + \fp_use:N \c__karol_wdx_fp }, { \fp_use:N \c__karol_wdy_fp } ) }
\cs_new:Npn \__karol_wecke_ho:
	{ ( { 1 + \fp_use:N \c__karol_wdx_fp },
	    { \fp_use:N \c__karol_wdy_fp + \__karol_wlage: } ) }
\cs_new:Npn \__karol_wecke_hl:
	{ ( { \fp_use:N \c__karol_wdx_fp },
	    { \fp_use:N \c__karol_wdy_fp + \__karol_wlage: } ) }
\cs_new:Npn \__karol_wseite_pfad:
	{ ( 1, 0 ) -- \__karol_wecke_vu: -- \__karol_wecke_ho:
	  -- ( 1, \__karol_wlage: ) -- cycle }
\cs_new:Npn \__karol_wdeckel_pfad:
	{ ( 0, \__karol_wlage: ) -- ( 1, \__karol_wlage: )
	  -- \__karol_wecke_ho: -- \__karol_wecke_hl: -- cycle }

% Andreaskreuz über die Sichtflächen eines Kubus (v3.9): „soll weg".
% Wie in der Vorlage bekommt JEDE Sichtfläche JEDER betroffenen Lage ihre
% beiden Diagonalen; die Deckfläche nur auf der obersten Lage, darunter
% verdeckt sie ohnehin der nächste Kubus.
%
% Strichstärke: in der Vorlage 6.5 px bei 120 px Zellbreite, also 0.054
% Feldbreiten (gemessen in ZielZiegelLoeschen.png, senkrechte Laufweite
% durch die Strichmitte geteilt durch sqrt(1+m^2)).  Bei 8.4 mm sind das
% 1.3 pt.  Wie alle Kanten des Pakets ein fester pt-Wert — er skaliert
% mit „skalierung“ (\scalebox), nicht mit „feldbreite“.
%
% KEIN „line cap=round“: die runde Kappe ragt eine halbe Strichstärke
% über die Ecke hinaus und schmiert auf die Nachbarzelle.  Die Vorlage
% hat stumpfe Enden.
\dim_const:Nn \c__karol_wkreuz_dim { 1.3pt }
\cs_new_protected:Npn \__karol_wkubus_kreuz:nnnn #1#2#3#4  % x,y,Lage,oberste?
{
	\begin{scope}
		[ shift={ ( { \__karol_wpx:nn { #1 - 1 } { \__karol_wtiefe:n {#2} } },
		            { \__karol_wpy:nnn { 0 } { \__karol_wtiefe:n {#2} }
		                                { #3 } } ) },
		  karolWKreuz, line~width=\c__karol_wkreuz_dim ]
		% Frontfläche
		\begin{scope}
			\clip ( 0, 0 ) rectangle ( 1, \__karol_wlage: );
			\draw ( 0, 0 ) -- ( 1, \__karol_wlage: );
			\draw ( 0, \__karol_wlage: ) -- ( 1, 0 );
		\end{scope}
		% rechte Seitenfläche
		\begin{scope}
			\clip \__karol_wseite_pfad: ;
			\draw ( 1, 0 ) -- \__karol_wecke_ho: ;
			\draw ( 1, \__karol_wlage: ) -- \__karol_wecke_vu: ;
		\end{scope}
		% Deckfläche: nur auf der obersten Lage sichtbar
		\int_compare:nT { #4 > 0 }
		{
			\begin{scope}
				\clip \__karol_wdeckel_pfad: ;
				\draw ( 0, \__karol_wlage: ) -- \__karol_wecke_ho: ;
				\draw ( 1, \__karol_wlage: ) -- \__karol_wecke_hl: ;
			\end{scope}
		}
	\end{scope}
}
% Ein Ziegel, der verschwinden soll: normale Lage plus Kreuz.
\cs_new_protected:Npn \__karol_wziegel_weg_iiid:nnnn #1#2#3#4
{
	\__karol_wziegel_iiid:nnn { #1 } { #2 } { #3 }
	\__karol_wkubus_kreuz:nnnn { #1 } { #2 } { #3 } { #4 }
}

% Marke: flache, einfarbige Platte auf Höhe \l__karol_wz_fp (kein Kreuz)
%
% Die Platte liegt ringsum \c__karol_wmr_fp Feldbreiten INNERHALB der
% Zelle — gerechnet in deren eigenen Koordinaten: u quer, v in die
% Tiefe, beide von 0 bis 1.  Ein Punkt (u,v) der Zellfläche liegt im
% Bild bei
%       x = u + v * wdx ,      y = v * wdy .
% Der Tiefenanteil v*wdx darf dabei NICHT fehlen.  Bis v3.7 stand für
% die vordere Kante schlicht x = 0.07: dadurch saß die ganze Platte um
% v*wdx (rund 0.034 Feldbreiten) zu weit links, berührte auf einem
% Ziegel dessen linke Kante und ließ rechts die doppelte Lücke.
\fp_const:Nn \c__karol_wmr_fp { 0.07 }   % Rand ringsum, in Feldbreiten
\cs_new:Npn \__karol_wmarke_pfad:
{
	\__karol_wmarke_ecke:nn { \c__karol_wmr_fp } { \c__karol_wmr_fp }
	-- \__karol_wmarke_ecke:nn { 1 - \c__karol_wmr_fp } { \c__karol_wmr_fp }
	-- \__karol_wmarke_ecke:nn { 1 - \c__karol_wmr_fp } { 1 - \c__karol_wmr_fp }
	-- \__karol_wmarke_ecke:nn { \c__karol_wmr_fp } { 1 - \c__karol_wmr_fp }
	-- cycle
}
% Zellkoordinate (#1 quer, #2 Tiefe) als Bildkoordinate
\cs_new:Npn \__karol_wmarke_ecke:nn #1#2
{
	( \fp_eval:n { (#1) + (#2) * \c__karol_wdx_fp } ,
	  \fp_eval:n { (#2) * \c__karol_wdy_fp } )
}
\cs_new_protected:Npn \__karol_wmarke_iiid:nnn #1#2#3
{
	% Im Druck wird jede Markenfarbe in ihren Grauwert umgesetzt: eigene
	% Farben bleiben so als verschiedene Grautöne unterscheidbar.
	\bool_if:NTF \l__karol_druck_bool
		{ \__karol_grau:n {#3} } { \colorlet{karolWMtmp}{#3} }
	\begin{scope}
		[ shift={ ( { \__karol_wpx:nn { #1 - 1 } { \__karol_wtiefe:n {#2} } },
		            { \__karol_wpy:nnn { 0 } { \__karol_wtiefe:n {#2} }
		                { \fp_use:N \l__karol_wz_fp } } ) } ]
		\fill [ karolWMtmp ] \__karol_wmarke_pfad: ;
		\draw [ karolWMtmp!62!black, line~width=.6pt ]
			\__karol_wmarke_pfad: ;
	\end{scope}
}

% Kreuz über eine Marke, die gelöscht werden soll.  Liegt in derselben
% Ebene wie die Platte, Diagonalen von Plattenecke zu Plattenecke — also
% ebenfalls in ZELLKOORDINATEN gerechnet (\__karol_wmarke_ecke:nn).
\cs_new_protected:Npn \__karol_wmarke_kreuz_iiid:nn #1#2
{
	\begin{scope}
		[ shift={ ( { \__karol_wpx:nn { #1 - 1 } { \__karol_wtiefe:n {#2} } },
		            { \__karol_wpy:nnn { 0 } { \__karol_wtiefe:n {#2} }
		                { \fp_use:N \l__karol_wz_fp } } ) },
		  karolWKreuz, line~width=\c__karol_wkreuz_dim ]
		\clip \__karol_wmarke_pfad: ;
		\draw
			\__karol_wmarke_ecke:nn { \c__karol_wmr_fp } { \c__karol_wmr_fp }
			-- \__karol_wmarke_ecke:nn { 1 - \c__karol_wmr_fp }
			                           { 1 - \c__karol_wmr_fp };
		\draw
			\__karol_wmarke_ecke:nn { 1 - \c__karol_wmr_fp } { \c__karol_wmr_fp }
			-- \__karol_wmarke_ecke:nn { \c__karol_wmr_fp }
			                           { 1 - \c__karol_wmr_fp };
	\end{scope}
}

% --- „soll entstehen" (v3.9) ----------------------------------------------
% X-Fläche einer Marke, die gesetzt werden soll: zwei Diagonalbänder, deren
% Spitzen in die vier Zellecken laufen.  Sechzehn Punkte in ZELLKOORDINATEN
% (u quer, v in die Tiefe), jeder über \__karol_wmarke_ecke:nn ins Bild
% abgebildet — der Tiefenanteil v*wdx darf auch hier nicht fehlen.
%
% Als Fläche und nicht als Strichpaar gezeichnet: eine Fläche skaliert mit
% „feldbreite“ mit, eine Strichstärke nicht.
% Die Punktliste steht einmal da; welche Abbildung sie ins Bild bringt,
% entscheidet der Aufrufer.  #1 ist ein Makro mit zwei Argumenten (u,v).
% So benutzen 3D- und 2D-Ansicht dieselbe Form.
\cs_new:Npn \__karol_wxliste:N #1
{
	#1 { 0 } { 0 }
	-- #1 { \c__karol_wxkt_fp } { 0 }
	-- #1 { 0.5 } { 0.5 - \c__karol_wxk_fp }
	-- #1 { 1 - \c__karol_wxkt_fp } { 0 }
	-- #1 { 1 } { 0 }
	-- #1 { 1 } { \c__karol_wxkt_fp }
	-- #1 { 0.5 + \c__karol_wxk_fp } { 0.5 }
	-- #1 { 1 } { 1 - \c__karol_wxkt_fp }
	-- #1 { 1 } { 1 }
	-- #1 { 1 - \c__karol_wxkt_fp } { 1 }
	-- #1 { 0.5 } { 0.5 + \c__karol_wxk_fp }
	-- #1 { \c__karol_wxkt_fp } { 1 }
	-- #1 { 0 } { 1 }
	-- #1 { 0 } { 1 - \c__karol_wxkt_fp }
	-- #1 { 0.5 - \c__karol_wxk_fp } { 0.5 }
	-- #1 { 0 } { \c__karol_wxkt_fp }
	-- cycle
}
% 3D: (u,v) sind Zellkoordinaten des Schrägbilds
\cs_new:Npn \__karol_wxecke:nn #1#2
	{ \__karol_wmarke_ecke:nn { \fp_eval:n {#1} } { \fp_eval:n {#2} } }
\cs_new:Npn \__karol_wxflaeche: { \__karol_wxliste:N \__karol_wxecke:nn }
% 2D: (u,v) laufen über das Quadrat der Markenfläche.  Dessen Lage steht
% in drei fp-Registern, weil die Punktliste keine Zellnummern kennt.
\fp_new:N \l__karol_wxox_fp
\fp_new:N \l__karol_wxoy_fp
\fp_new:N \l__karol_wxs_fp
\cs_new:Npn \__karol_wxecke_iid:nn #1#2
{
	( \fp_eval:n { \l__karol_wxox_fp + \l__karol_wxs_fp * (#1) } ,
	  \fp_eval:n { \l__karol_wxoy_fp + \l__karol_wxs_fp * (#2) } )
}
\cs_new:Npn \__karol_wxflaeche_iid: { \__karol_wxliste:N \__karol_wxecke_iid:nn }
\cs_new_protected:Npn \__karol_wmarke_neu_iiid:nnn #1#2#3
{
	\bool_if:NTF \l__karol_druck_bool
		{ \colorlet{karolWMtmp}{white} } { \colorlet{karolWMtmp}{#3} }
	\__karol_wneu_deckkraft:
	\begin{scope}
		[ shift={ ( { \__karol_wpx:nn { #1 - 1 } { \__karol_wtiefe:n {#2} } },
		            { \__karol_wpy:nnn { 0 } { \__karol_wtiefe:n {#2} }
		                { \fp_use:N \l__karol_wz_fp } } ) },
		  transparency~group, opacity=\l__karol_wneudeck_tl ]
		\fill [ karolWMtmp ] \__karol_wxflaeche: ;
		% Farbmodus: Randfarbe wie bei der vollen Platte — die beiden
		% Markenformen stehen oft nebeneinander und sollen zusammengehören.
		% Druck: weiße Fläche, gestrichelte Kontur (siehe oben).
		\bool_if:NTF \l__karol_druck_bool
		{
			\draw [ karolDruckKontur, line~width=.6pt,
			        dash~pattern = on~1.2pt~off~1pt ]
				\__karol_wxflaeche: ;
		}
		{
			\draw [ karolWMtmp!62!black, line~width=.6pt ]
				\__karol_wxflaeche: ;
		}
	\end{scope}
}

% Pfeil, der auf einen entstehenden Ziegel zeigt.  Steht aufrecht in der
% Bildebene (wie der Avatar), nicht in der Zellebene.  Sieben Punkte,
% Ursprung ist die Mitte der Zellfläche.
% Deckkraft der Geisterdarstellung.  Steht in einem tl-Register, weil die
% Scope-Optionen sie brauchen — ein Test im Schlüsselwert wäre der
% Stolperstein aus dem Kopf dieser Datei.
\tl_new:N \l__karol_wneudeck_tl
\cs_new_protected:Npn \__karol_wneu_deckkraft:
{
	\bool_if:NTF \l__karol_druck_bool
		{ \tl_set:Nn \l__karol_wneudeck_tl { 1 } }
		{ \tl_set:Nx \l__karol_wneudeck_tl { \fp_use:N \c__karol_wneu_fp } }
}
% Der Kantenstil dagegen muss INNERHALB der Gruppe gesetzt werden, damit
% er nur die Geisterlage trifft und nicht die echten Ziegel daneben.
\cs_new_protected:Npn \__karol_wneu_kante:
{
	\bool_if:NT \l__karol_druck_bool
	{
		\tikzset { karolgeistkante / .style =
			{ dash~pattern = on~1.2pt~off~1pt } }
	}
}

% Ein Punkt des Pfeils, in Vielfachen der Pfeilhöhe, Ursprung = Spitze.
% Die Maße sind fp-Konstanten — im Bildkontext MUSS jede davon durch
% \fp_eval:n, eine nackte fp-Variable versteht pgfmath nicht
% (Stolperstein, kostet eine „Undefined control sequence").
\cs_new:Npn \__karol_wpfeil_punkt:nn #1#2
{
	( \fp_eval:n { (#1) * \c__karol_wpfhoehe_fp } ,
	  \fp_eval:n { (#2) * \c__karol_wpfhoehe_fp } )
}
\cs_new:Npn \__karol_wpfeil_schaft:
{
	\__karol_wpfeil_punkt:nn { 0 } { \c__karol_wpfsu_fp }
	-- \__karol_wpfeil_punkt:nn { 0 } { \c__karol_wpfso_fp }
}
\cs_new:Npn \__karol_wpfeil_fluegel:
{
	\__karol_wpfeil_punkt:nn { 0 } { \c__karol_wpffj_fp }
	-- \__karol_wpfeil_punkt:nn { -\c__karol_wpffx_fp } { \c__karol_wpffy_fp }
	\__karol_wpfeil_punkt:nn { 0 } { \c__karol_wpffj_fp }
	-- \__karol_wpfeil_punkt:nn { \c__karol_wpffx_fp } { \c__karol_wpffy_fp }
}
% Strichstärken: der Pfeil ist eine FLÄCHE, keine Haarlinie — seine
% Breiten werden deshalb aus der Feldbreite gerechnet und wachsen mit
% ihr mit.  (Die Kanten der Ziegel sind feste pt-Werte; das ist bei
% 0.5 pt egal, bei einem 3 pt dicken Schaft nicht.)
\dim_new:N \l__karol_wpfws_dim      % Schaft
\dim_new:N \l__karol_wpfwf_dim      % Flügel
\dim_const:Nn \c__karol_wpfrand_dim { 0.5pt }   % Kontur je Seite
\cs_new_protected:Npn \__karol_wpfeil_masse:
{
	\dim_set:Nn \l__karol_wpfws_dim
		{ \fp_to_dim:n { \c__karol_wpfschaft_fp * \c__karol_wpfhoehe_fp
		                 * \dim_to_fp:n { \g__karol_feld_dim } } }
	\dim_set:Nn \l__karol_wpfwf_dim
		{ \fp_to_dim:n { \c__karol_wpffl_fp * \c__karol_wpfhoehe_fp
		                 * \dim_to_fp:n { \g__karol_feld_dim } } }
}
% Der Pfeil wird zweimal gestrichen: erst alle drei Striche dick und
% dunkel, dann dieselben dünner und hell darüber.  So bekommt die
% VEREINIGUNG der drei Striche eine saubere Kontur — ein Polygon
% müsste dafür die Rundungen selbst ausrechnen.
\cs_new_protected:Npn \__karol_wpfeil_zeichnen:
{
	\draw [ karolWPfeilKante, line~cap=round, line~join=round,
	        line~width=\dim_eval:n { \l__karol_wpfws_dim
	                                 + 2 \c__karol_wpfrand_dim } ]
		\__karol_wpfeil_schaft: ;
	\draw [ karolWPfeilKante, line~cap=round, line~join=round,
	        line~width=\dim_eval:n { \l__karol_wpfwf_dim
	                                 + 2 \c__karol_wpfrand_dim } ]
		\__karol_wpfeil_fluegel: ;
	\draw [ karolWPfeil, line~cap=round, line~join=round,
	        line~width=\l__karol_wpfws_dim ]
		\__karol_wpfeil_schaft: ;
	\draw [ karolWPfeil, line~cap=round, line~join=round,
	        line~width=\l__karol_wpfwf_dim ]
		\__karol_wpfeil_fluegel: ;
}

% #3 ist die Lage, auf der die Einheit sitzt — sie DARF gebrochen sein:
% die Geistereinheiten stehen nicht im Raster der Ziegellagen, sondern
% im Raster ihrer eigenen Höhe (siehe \__karol_wzelle_iiid:nn).
\cs_new_protected:Npn \__karol_wziegel_neu_iiid:nnn #1#2#3
{
	\__karol_wneu_deckkraft:
	\__karol_wpfeil_masse:
	\begin{scope}
		[ transparency~group, opacity=\l__karol_wneudeck_tl ]
		\__karol_wneu_kante:
		% Der Mini-Ziegel ist derselbe Kubus, durch die Abbildung
		%   P -> A + s * (P - O)
		% geschickt (shift = A - s*O, scale = s).  O ist der Zellursprung,
		% A der gewünschte Ursprung des verkleinerten Ziegels: von der
		% Pfeilspitze aus um (wminidx, wminidy) versetzt.  Die Spitze liegt
		% in der Mitte der Zellfläche, also
		%   O = Mitte - (0.5 + 0.5*wdx, 0.5*wdy).
		\begin{scope}
			[ shift={ ( { \fp_eval:n
			                { ( 1 - \c__karol_wmini_fp )
			                    * \__karol_wmitte_x:nn { #1 } { #2 }
			                  + \c__karol_wminidx_fp
			                  + \c__karol_wmini_fp
			                    * ( 0.5 + 0.5 * \c__karol_wdx_fp ) } },
			            { \fp_eval:n
			                { ( 1 - \c__karol_wmini_fp )
			                    * \__karol_wmitte_y:nnn { #1 } { #2 } { #3 }
			                  + \c__karol_wminidy_fp
			                  + \c__karol_wmini_fp
			                    * 0.5 * \c__karol_wdy_fp } } ) },
			  scale=\fp_use:N \c__karol_wmini_fp ]
			\__karol_wziegel_iiid:nnn { #1 } { #2 } { #3 }
		\end{scope}
		\begin{scope}
			[ shift={ ( { \__karol_wmitte_x:nn { #1 } { #2 } },
			            { \__karol_wmitte_y:nnn { #1 } { #2 } { #3 } } ) } ]
			\__karol_wpfeil_zeichnen:
		\end{scope}
	\end{scope}
}
% Bildmitte der Zellfläche auf Lage #3
\cs_new:Npn \__karol_wmitte_x:nn #1#2
{
	\fp_eval:n { \__karol_wpx:nn { #1 - 1 } { \__karol_wtiefe:n {#2} }
	             + 0.5 + 0.5 * \c__karol_wdx_fp }
}
\cs_new:Npn \__karol_wmitte_y:nnn #1#2#3
{
	\fp_eval:n { \__karol_wpy:nnn { 0 } { \__karol_wtiefe:n {#2} } { #3 }
	             + 0.5 * \c__karol_wdy_fp }
}

% Avatar (Vektorfassung des Karol-Online-Avatars), Blick per wkdir
\cs_new_protected:Npn \__karol_wavatar_iiid:nn #1#2
{
	\begin{scope}
		[ shift={ ( { \__karol_wpx:nn { #1 - 0.5 } { \__karol_wtiefe:n {#2} }
		              + 0.5 * \fp_use:N \c__karol_wdx_fp },
		            { \__karol_wpy:nnn { 0 } { \__karol_wtiefe:n {#2} }
		                { \fp_use:N \l__karol_wz_fp }
		              + 0.5 * \fp_use:N \c__karol_wdy_fp } ) },
		  scale=1.45,
		  xscale={ \str_if_eq:VnTF \l__karol_wkdir_tl { w } { -1 } { 1 } } ]
		\__karol_wavatar_zeichnen:V \l__karol_wkdir_tl
	\end{scope}
}
% Zeichnet die Figur.  #1 = Blickrichtung (o/w/n/s).
% Beide Figuren teilen sich denselben Körper; „emma" bekommt zusätzlich
% langes Haar, eine Blume in der Hand und ein helleres Hemd.
\cs_new_protected:Npn \__karol_wavatar_zeichnen:n #1
{
	\bool_set_false:N \l__karol_wemma_bool
	\str_if_eq:VnT \l__karol_wfigur_tl { emma }
		{ \bool_set_true:N \l__karol_wemma_bool }
	\bool_if:NTF \l__karol_wemma_bool
		{ \colorlet { karolAvatarHemd } { karolEmmaShirt  } }
		{ \colorlet { karolAvatarHemd } { karolAvatarShirt } }
	% Beine
	\fill [ karolAvatarHose, rounded~corners=.6pt ]
		(-0.17, 0) rectangle (-0.03, 0.34)
		( 0.03, 0) rectangle ( 0.17, 0.34);
	\fill [ black!75 ]
		(-0.19, 0) rectangle (-0.01, 0.055)
		( 0.01, 0) rectangle ( 0.19, 0.055);
	% Rumpf
	\fill [ karolAvatarHemd, rounded~corners=1pt ]
		(-0.21, 0.32) rectangle (0.21, 0.72);
	% Arme
	\fill [ karolAvatarHemd, rounded~corners=.8pt ]
		(-0.29, 0.36) rectangle (-0.19, 0.70)
		( 0.19, 0.36) rectangle ( 0.29, 0.70);
	\fill [ karolAvatarHaut ]
		(-0.285, 0.335) rectangle (-0.195, 0.42)
		( 0.195, 0.335) rectangle ( 0.285, 0.42);
	% Kopf
	\str_if_eq:nnTF {#1} { n }
	{	% Rückenansicht: Haar statt Gesicht
		\fill [ karolAvatarHaar, rounded~corners=1.2pt ]
			(-0.185, 0.70) rectangle (0.185, 0.97);
	}
	{
		\fill [ karolAvatarHaut, rounded~corners=1.2pt ]
			(-0.185, 0.70) rectangle (0.185, 0.97);
		\str_case:nn {#1}
		{
			{ s } {	% zugewandt: zwei Augen, Mund
				\fill [ black!80 ]
					(-0.075, 0.865) circle (0.022)
					( 0.075, 0.865) circle (0.022);
				\draw [ black!60, line~width=.5pt ]
					(-0.05, 0.775) -- (0.05, 0.775);
			}
			{ o } {	% Profil (Osten; Westen via xscale=-1)
				\fill [ black!80 ] (0.10, 0.865) circle (0.022);
				\draw [ black!60, line~width=.5pt ]
					(0.06, 0.775) -- (0.15, 0.775);
				\__karol_wavatar_haar_kurz:
			}
			{ w } {
				\fill [ black!80 ] (0.10, 0.865) circle (0.022);
				\draw [ black!60, line~width=.5pt ]
					(0.06, 0.775) -- (0.15, 0.775);
				\__karol_wavatar_haar_kurz:
			}
		}
	}
	% Emma: langes Haar.  Nach dem Kopf gezeichnet, damit es das Gesicht
	% rahmt statt dahinter zu verschwinden — aber schmal genug, um Augen
	% und Mund frei zu lassen.
	%
	% Alle Strähnen bleiben innerhalb der Mütze (x von -0.20 bis 0.20)
	% und enden oben unter deren Unterkante: die Mütze wird danach
	% gezeichnet und deckt sie ab.  Ragt das Haar seitlich darüber
	% hinaus, sieht es aus, als läge es auf der Mütze.
	\bool_if:NT \l__karol_wemma_bool
	{
		\str_case:nn {#1}
		{
			{ n } {	% abgewandt: das Haar bedeckt Kopf und Rücken
				\fill [ karolEmmaHaar, rounded~corners=1.6pt ]
					(-0.20, 0.38) rectangle (0.20, 1.00);
			}
			{ s } {	% zugewandt: lange Strähne links, kurze rechts
				\fill [ karolEmmaHaar, rounded~corners=1.2pt ]
					(-0.195, 0.44) rectangle (-0.125, 0.98);
				\fill [ karolEmmaHaar, rounded~corners=1pt ]
					( 0.135, 0.73) rectangle ( 0.195, 0.98);
			}
			{ o } {	% Profil: das Haar fällt hinten (links) herab
				\fill [ karolEmmaHaar, rounded~corners=1.2pt ]
					(-0.195, 0.40) rectangle (-0.105, 0.99);
			}
			{ w } {
				\fill [ karolEmmaHaar, rounded~corners=1.2pt ]
					(-0.195, 0.40) rectangle (-0.105, 0.99);
			}
		}
	}
	% Kappe: Deckel + Schirm in Blickrichtung
	\fill [ karolAvatarKappe, rounded~corners=1.4pt ]
		(-0.20, 0.93) rectangle (0.20, 1.08);
	\str_case:nn {#1}
	{
		{ o } { \fill [ karolAvatarKappe, rounded~corners=.5pt ]
			(0.17, 0.93) rectangle (0.40, 0.985); }
		{ w } { \fill [ karolAvatarKappe, rounded~corners=.5pt ]
			(0.17, 0.93) rectangle (0.40, 0.985); }
		{ s } { \fill [ karolAvatarKappe!85!black, rounded~corners=.5pt ]
			(-0.21, 0.905) rectangle (0.21, 0.955); }
		{ n } { }
	}
	% Emma: Blume in der Hand — zuletzt, damit sie vor allem anderen liegt
	\bool_if:NT \l__karol_wemma_bool
	{
		\fill [ karolEmmaStiel, rounded~corners=.3pt ]
			(0.222, 0.38) rectangle (0.258, 0.62);
		\fill [ karolEmmaBluete ] (0.24, 0.655) circle (0.072);
		\fill [ karolEmmaBluete!62!black ] (0.24, 0.655) circle (0.026);
	}
}
% Kurzes Haar am Hinterkopf (nur die Figur „karol"; bei „emma" verdeckt
% es das lange Haar ohnehin)
\cs_new_protected:Npn \__karol_wavatar_haar_kurz:
{
	\bool_if:NF \l__karol_wemma_bool
	{
		\fill [ karolAvatarHaar ]
			(-0.185, 0.70) rectangle (-0.10, 0.95);
	}
}
\cs_generate_variant:Nn \__karol_wavatar_zeichnen:n { V }

% --- Umgebung --------------------------------------------------------------
\NewDocumentEnvironment { karolwelt } { O{} }
{
	\__karol_farbschema:
	\__karol_wobjekte:
	\keys_set:nV { karol / weltumg } \c__karol_wvorgabe_tl
	\keys_set:nn { karol / weltumg } { #1 }
	\prop_clear:N \l__karol_wziegel_prop
	\prop_clear:N \l__karol_wzziegel_prop
	\prop_clear:N \l__karol_wmarke_prop
	\prop_clear:N \l__karol_wzmarke_prop
	\prop_clear:N \l__karol_wquader_prop
	\bool_set_false:N \l__karol_wkarol_bool
	\ignorespaces
}
{
	\par \mode_leave_vertical:
	\begin{tabular} [b] {@{}c@{}}
		\tl_if_empty:NF \l__karol_wtitel_tl
			{ { \footnotesize \l__karol_wtitel_tl } \\ [2pt] }
		\exp_args:NV \scalebox \l__karol_wskal_tl
		{
			\str_if_eq:VnTF \l__karol_wansicht_tl { 2d }
				{ \__karol_wrender_iid: }
				{ \__karol_wrender_iiid: }
		}
		\tl_if_empty:NF \l__karol_whinweis_tl
			{ \\ [3pt] { \footnotesize \itshape \l__karol_whinweis_tl } }
	\end{tabular}
}

% ---------------------------------------------------------------------------
%  Zwei Welten nebeneinander: karolweltpaar
% ---------------------------------------------------------------------------
%  Derselbe Rumpf, zweimal gezeichnet — links der Start, rechts das Ziel.
%  „Vorher / nachher" ist auf Arbeitsblättern der Normalfall; ohne diese
%  Umgebung stünde die Welt zweimal im Dokument und beide liefen bei der
%  ersten Korrektur auseinander.
%
%  Der Rumpf wird als +b-Argument gegriffen und ZWEIMAL ausgeführt.  Das
%  geht, weil er nur Objektbefehle enthält, die Register setzen — jede
%  karolwelt-Umgebung bildet ihre eigene Gruppe und leert ihre Props
%  selbst.
\tl_new:N \l__karol_wplinks_tl
\tl_new:N \l__karol_wprechts_tl
\tl_new:N \l__karol_wptrenner_tl
\tl_new:N \l__karol_wptlinks_tl
\tl_new:N \l__karol_wptrechts_tl
\tl_new:N \l__karol_wprest_tl
\tl_new:N \l__karol_wpkeys_tl
\keys_define:nn { karol / weltpaar }
{
	links       .tl_set:N = \l__karol_wplinks_tl,
	rechts      .tl_set:N = \l__karol_wprechts_tl,
	trenner     .tl_set:N = \l__karol_wptrenner_tl,
	titellinks  .tl_set:N = \l__karol_wptlinks_tl,
	titelrechts .tl_set:N = \l__karol_wptrechts_tl,
}
\tl_const:Nn \c__karol_wpvorgabe_tl
{
	links = start, rechts = ziel,
	trenner = { \large \faArrowRight },
	titellinks = { \textbf{Start} }, titelrechts = { \textbf{Ziel} },
}
% Eine Seite setzen: #1 = fertige Schlüsselliste, #2 = Rumpf
\cs_new_protected:Npn \__karol_wpaar_seite:nn #1#2
{
	\begin{karolwelt} [ #1 ] #2 \end{karolwelt}
}
% Schlüsselliste einer Seite bauen: eigene Schlüssel des Paares zuletzt,
% damit sie eine gleichnamige Angabe des Nutzers nicht überschreiben.
\cs_new_protected:Npn \__karol_wpaar_liste:NNN #1#2#3
{
	\tl_set_eq:NN #1 \l__karol_wprest_tl
	\tl_put_right:Nn #1 { , ~ zustand = }
	\tl_put_right:NV #1 #2
	\tl_put_right:Nn #1 { , ~ titel = }
	\tl_put_right:Nx #1 { { \exp_not:V #3 } }
}
\NewDocumentEnvironment { karolweltpaar } { O{} +b }
{
	\group_begin:
		\keys_set:nV { karol / weltpaar } \c__karol_wpvorgabe_tl
		\keys_set_known:nnN { karol / weltpaar } { #1 } \l__karol_wprest_tl
		\begin{center}
			\begin{tabular} [b] {@{}c@{\hspace{1.1em}}c@{\hspace{1.1em}}c@{}}
				\__karol_wpaar_liste:NNN \l__karol_wpkeys_tl
					\l__karol_wplinks_tl \l__karol_wptlinks_tl
				\exp_args:NV \__karol_wpaar_seite:nn \l__karol_wpkeys_tl {#2}
				&
				\raisebox{1.4em}{\l__karol_wptrenner_tl}
				&
				\__karol_wpaar_liste:NNN \l__karol_wpkeys_tl
					\l__karol_wprechts_tl \l__karol_wptrechts_tl
				\exp_args:NV \__karol_wpaar_seite:nn \l__karol_wpkeys_tl {#2}
				\\
			\end{tabular}
		\end{center}
	\group_end:
}
{ }

% ---------------------------------------------------------------------------
%  Legende der Zustände
% ---------------------------------------------------------------------------
%  Geschwister von \karollegende (Abschnitt 4b), aber aus der Umgebung
%  gebaut: \karolzeile kennt die Zustände nicht und soll sie nicht kennen.
%  Die unveränderten Symbole erklärt weiterhin \karollegende.
\NewDocumentCommand { \karolzustandslegendenitem } { m m }
{
	\mbox
	{
		% Etwas größer als \karollegendenitem: Kreuz, Pfeil und X-Fläche
		% sind feiner als ein voller Ziegel und bei 0.42 kaum zu erkennen.
		\begin{karolwelt} [ breite=1, laenge=1, rahmen=false,
		                    skalierung=0.5 ]
			#1
		\end{karolwelt}
		\, #2
	}
}
\NewDocumentCommand { \karolzustandslegende } { }
{
	% Achtung: unter \ExplSyntaxOn ist ein Leerzeichen wirkungslos —
	% die Beschriftungen brauchen ~ als Wortzwischenraum.
	\karolzustandslegendenitem { \ziegel[x=1,y=1,hinlegen] }
		{ soll~hingelegt~werden } \karollegendentrenner
	\karolzustandslegendenitem { \ziegel[x=1,y=1,aufheben] }
		{ soll~aufgehoben~werden } \karollegendentrenner
	\karolzustandslegendenitem { \marke[x=1,y=1,setzen] }
		{ soll~gesetzt~werden } \karollegendentrenner
	\karolzustandslegendenitem { \marke[x=1,y=1,loeschen] }
		{ soll~gelöscht~werden }
}

% ===========================================================================
%  4b. Zeilen-Welten: \karolzeile
% ===========================================================================
%  \karolzeile{> Z 2 {2M} M Q} zeichnet eine EINZELNE Feldreihe aus einer
%  Zeichenkette.  Sie ist der schnelle Weg für Arbeitsblätter, bei denen
%  die Tiefe der Welt keine Rolle spielt.
%
%  Gezeichnet wird mit denselben Bausteinen wie die karolwelt-Umgebung:
%  gleiche Projektion, gleiches Bodengitter, gleiche Ziegel, Marken,
%  Quader und derselbe Avatar.  Eine Zeilen-Welt ist damit optisch nichts
%  anderes als eine karolwelt mit laenge=1 — nur die Eingabe ist kürzer.
%
%  Zeichen:  .  leeres Feld          >  Karol nach Osten
%            Z  ein Ziegel           <  Karol nach Westen
%            1…9 Ziegelstapel        ^  Karol nach Norden (abgewandt)
%            M  Marke                v  Karol nach Süden (zugewandt)
%            Q  Quader / Wand
%  Mehrere Zeichen auf einem Feld werden geklammert: {2M} = zwei Ziegel
%  mit einer Marke obenauf.  Die Zeichen eines Feldes werden von unten
%  nach oben abgearbeitet.
% ---------------------------------------------------------------------------
\int_new:N   \l__karol_i_int        % aktueller Feldindex (0-basiert)
\int_new:N   \l__karol_n_int        % Feldanzahl
\tl_new:N    \l__karol_skalierung_tl
\tl_new:N    \l__karol_hinweis_tl
\tl_new:N    \l__karol_titel_tl
\bool_new:N  \l__karol_nummern_bool
\bool_new:N  \l__karol_rahmen_bool
\int_new:N   \l__karol_zhoehe_int   % Raumhöhe des optionalen Rahmens
\clist_new:N \l__karol_hervor_clist

\keys_define:nn { karol / welt }
{
	skalierung  .tl_set:N    = \l__karol_skalierung_tl,
	skalierung  .initial:n   = { 1 },
	nummern     .bool_set:N  = \l__karol_nummern_bool,
	nummern     .default:n   = true,
	nummern     .initial:n   = true,
	hinweis     .tl_set:N    = \l__karol_hinweis_tl,
	hinweis     .initial:n   = { },
	titel       .tl_set:N    = \l__karol_titel_tl,
	titel       .initial:n   = { },
	hervorheben .clist_set:N = \l__karol_hervor_clist,
	hervorheben .initial:n   = { },
	% Raumrahmen wie in der karolwelt-Umgebung.  Für eine einzelne Reihe
	% ist er meist zu wuchtig (und in Legenden störend), deshalb aus.
	rahmen      .bool_set:N  = \l__karol_rahmen_bool,
	rahmen      .default:n   = true,
	rahmen      .initial:n   = false,
	hoehe       .int_set:N   = \l__karol_zhoehe_int,
	hoehe       .initial:n   = { 3 },
}

% --- Feldnummern ------------------------------------------------------------
% Die Nummern stehen VOR der vordersten Gitterkante, nicht auf dem Feld:
% so verdeckt sie kein Ziegelstapel und die Zuordnung Zahl <-> Feld bleibt
% auch in vollen Welten eindeutig.
%
% Abstand der Ziffernoberkante von der Gitterkante, in Feldbreiten.  Er
% muss den Hervorhebungsrahmen mit einschließen: der liegt 0.06 unterhalb
% der Zelle (\__karol_zhervor:n) und ist 1.2pt stark, reicht also gut
% 0.066 Feldbreiten unter die Kante.  Sonst berührt die Ziffer ihn.
\fp_const:Nn \c__karol_znummer_fp { 0.15 }
\tl_new:N \l__karol_znfarbe_tl
\cs_new_protected:Npn \__karol_znummer:n #1        % #1 = Feldindex
{
	% Hervorgehobene Felder bekommen auch eine rote Nummer — der rote
	% Bodenrahmen kann von hohen Stapeln verdeckt sein.
	\clist_if_in:NnTF \l__karol_hervor_clist {#1}
		{ \tl_set:Nn \l__karol_znfarbe_tl { karolWHervor } }
		{ \tl_set:Nn \l__karol_znfarbe_tl { karolWGitter } }
	\node [ font=\__karol_textfont:\bfseries\tiny, inner~sep=0pt, anchor=north,
	        text=\l__karol_znfarbe_tl ]
		at ( { (#1) + 0.5 },
		     { -\fp_use:N \c__karol_znummer_fp } ) { \int_eval:n {#1} };
}

% --- Hervorhebung einzelner Felder -----------------------------------------
% Rotes Feldparallelogramm auf dem Boden (wird vor den Objekten gezeichnet,
% die Objekte stehen also darauf).
\cs_new_protected:Npn \__karol_zhervor:n #1
{
	% Der Rahmen liegt 0.06 Feldbreiten außerhalb der Zelle: so bleibt er
	% auch dann sichtbar, wenn ein Ziegelstapel oder Quader auf dem Feld
	% steht (die Objekte füllen die Zelle randlos aus).
	\draw [ karolWHervor, line~width=1.2pt, line~join=round ]
		( \__karol_wpx:nn { #1 - 0.06 }     { -0.06 } ,
		  \__karol_wpy:nnn { 0 } { -0.06 } { 0 } )
		-- ( \__karol_wpx:nn { #1 + 1.06 }  { -0.06 } ,
		     \__karol_wpy:nnn { 0 } { -0.06 } { 0 } )
		-- ( \__karol_wpx:nn { #1 + 1.06 }  { 1.06 } ,
		     \__karol_wpy:nnn { 0 } { 1.06 } { 0 } )
		-- ( \__karol_wpx:nn { #1 - 0.06 }  { 1.06 } ,
		     \__karol_wpy:nnn { 0 } { 1.06 } { 0 } )
		-- cycle;
}

% --- Zellinhalt: ein Element ------------------------------------------------
% Alle Elemente sitzen auf dem Höhencursor \l__karol_wz_fp (in Ziegellagen)
% und schieben ihn — genau wie bisher — um ihre eigene Bauhöhe weiter.
\cs_new_protected:Npn \__karol_zziegel:
{
	\__karol_wziegel_iiid:nnn { \l__karol_i_int + 1 } { 1 }
		{ \fp_use:N \l__karol_wz_fp }
	\fp_add:Nn \l__karol_wz_fp { 1 }
}
\cs_new_protected:Npn \__karol_zstapel:n #1
	{ \prg_replicate:nn {#1} { \__karol_zziegel: } }
\cs_new_protected:Npn \__karol_zquader:
{
	\__karol_wquader_iiid:nnn { \l__karol_i_int + 1 } { 1 }
		{ \fp_use:N \l__karol_wz_fp }
	\fp_add:Nn \l__karol_wz_fp { \c__karol_wqh_fp / \c__karol_wzh_fp }
}
% Die Marke ist — wie in der karolwelt-Umgebung — eine flache Platte ohne
% Bauhöhe; sie schiebt den Cursor daher nicht weiter.  Karol steht auf ihr.
\cs_new_protected:Npn \__karol_zmarke:
	{ \__karol_wmarke_iiid:nnn { \l__karol_i_int + 1 } { 1 } { karolWMarke } }
\cs_new_protected:Npn \__karol_zfigur:n #1
{
	\tl_set:Nn \l__karol_wkdir_tl {#1}
	\__karol_wavatar_iiid:nn { \l__karol_i_int + 1 } { 1 }
}

\cs_new_protected:Npn \__karol_element:n #1
{
	\str_case:nnF {#1}
	{
		{ . } { }
		{ > } { \__karol_zfigur:n { o } }
		{ < } { \__karol_zfigur:n { w } }
		{ ^ } { \__karol_zfigur:n { n } }
		{ v } { \__karol_zfigur:n { s } }
		{ Z } { \__karol_zziegel: }
		{ 1 } { \__karol_zstapel:n { 1 } }
		{ 2 } { \__karol_zstapel:n { 2 } }
		{ 3 } { \__karol_zstapel:n { 3 } }
		{ 4 } { \__karol_zstapel:n { 4 } }
		{ 5 } { \__karol_zstapel:n { 5 } }
		{ 6 } { \__karol_zstapel:n { 6 } }
		{ 7 } { \__karol_zstapel:n { 7 } }
		{ 8 } { \__karol_zstapel:n { 8 } }
		{ 9 } { \__karol_zstapel:n { 9 } }
		{ M } { \__karol_zmarke: }
		{ Q } { \__karol_zquader: }
	}
	{
		\msg_warning:nnn { karol } { unbekanntes-zeichen } {#1}
	}
}
\msg_new:nnn { karol } { unbekanntes-zeichen }
	{ Unbekanntes~Weltzeichen~"#1"~-~wird~ignoriert. }

% --- Zelle: Inhaltsstapel über dem Bodenfeld -------------------------------
\cs_new_protected:Npn \__karol_zelle:n #1
{
	\fp_zero:N \l__karol_wz_fp
	\tl_map_function:nN {#1} \__karol_element:n
	\int_incr:N \l__karol_i_int
}

% ---------------------------------------------------------------------------
%  \karolzeile[keys]{Zeichenkette}
% ---------------------------------------------------------------------------
\NewDocumentCommand { \karolzeile } { O{} m }
{
	\group_begin:
		\keys_set:nn { karol / welt } { #1 }
		\__karol_farbschema:
		\int_set:Nn \l__karol_n_int { \tl_count:n { #2 } }
		\int_zero:N \l__karol_i_int
		% Eine Zeilen-Welt ist eine karolwelt mit genau einer Reihe:
		% damit rechnen die gemeinsamen Bausteine korrekt.
		\int_set_eq:NN \l__karol_wbreite_int \l__karol_n_int
		\int_set:Nn    \l__karol_wlaenge_int { 1 }
		\int_set_eq:NN \l__karol_whoehe_int  \l__karol_zhoehe_int
		\mode_leave_vertical:
		\begin{tabular} [b] {@{}c@{}}
			\tl_if_empty:NF \l__karol_titel_tl
				{ { \footnotesize \l__karol_titel_tl } \\[2pt] }
			\scalebox { \l__karol_skalierung_tl }
			{
				\begin{tikzpicture}
					[ x = \g__karol_feld_dim, y = \g__karol_feld_dim,
					  baseline = 0pt ]
					% unsichtbare Mindesthöhe, damit flache Welten im Paar
					% nicht „springen":
					\path (0, 0) -- ( 0,
						{ \fp_eval:n { \c__karol_wdy_fp
						               + 1.5 * \c__karol_wzh_fp } } );
					\__karol_wboden_gitter:
					\bool_if:NT \l__karol_rahmen_bool { \__karol_wrahmen: }
					\clist_map_function:NN \l__karol_hervor_clist
						\__karol_zhervor:n
					\tl_map_function:nN { #2 } \__karol_zelle:n
					\bool_if:NT \l__karol_nummern_bool
					{
						\int_step_inline:nnn { 0 } { \l__karol_n_int - 1 }
							{ \__karol_znummer:n { ##1 } }
					}
				\end{tikzpicture}
			}
			\tl_if_empty:NF \l__karol_hinweis_tl
				{ \\[3pt] { \footnotesize \itshape \l__karol_hinweis_tl } }
		\end{tabular}
	\group_end:
}

% ---------------------------------------------------------------------------
%  \karolgitter[keys]{Anzahl Felder} — leere Felder zum Eintragen
% ---------------------------------------------------------------------------
\tl_new:N \l__karol_opt_tl
\NewDocumentCommand { \karolgitter } { O{} m }
{
	\group_begin:
		\tl_set:Nn \l__karol_opt_tl { #1 }
		\tl_set:Nx \l__karol_tmp_tl { \prg_replicate:nn { #2 } { . } }
		\use:x
		{
			\exp_not:N \karolzeile [ \exp_not:V \l__karol_opt_tl ]
				{ \exp_not:V \l__karol_tmp_tl }
		}
	\group_end:
}


\ExplSyntaxOff

% ---------------------------------------------------------------------------
%  \karolpaar[gemeinsame keys][keys nur für „nachher"]{vorher}{nachher}
% ---------------------------------------------------------------------------
\providecommand{\faArrowRight}{\ensuremath{\rightarrow}}
\NewDocumentCommand{\karolpaar}{ O{} O{} m m }{%
	\begin{center}
		\begin{tabular}[b]{@{}c@{\hspace{1.1em}}c@{\hspace{1.1em}}c@{}}
			\karolzeile[#1, titel={\textbf{vorher}}]{#3} &
			\raisebox{1.4em}{\large\faArrowRight} &
			\karolzeile[#1, titel={\textbf{nachher}}, #2]{#4} \\
		\end{tabular}
	\end{center}%
}

% ---------------------------------------------------------------------------
%  Legende der Weltsymbole
% ---------------------------------------------------------------------------
% Ein Legendeneintrag: \karollegendenitem{Weltstring}{Beschriftung}
% Die Einträge sind bewusst klein skaliert: im Schrägbild der karolwelt
% braucht ein Feld mehr Fläche als in der früheren flachen Darstellung.
\newcommand{\karollegendenitem}[2]{%
	\mbox{\karolzeile[nummern=false, skalierung=0.42]{#1}\,#2}%
}
% Trenner mit viel Dehnung und leichtem Umbruchanreiz: die Legende ist
% breit und soll notfalls über mehrere Zeilen laufen statt überzustehen.
\newcommand{\karollegendentrenner}{%
	\penalty0\hskip 0.9em plus 2.5em minus .25em\relax}
\newcommand{\karollegende}{%
	\karollegendenitem{>}{Karol (Blickrichtung)}\karollegendentrenner
	\karollegendenitem{Z}{Ziegel}\karollegendentrenner
	\karollegendenitem{3}{Ziegelstapel}\karollegendentrenner
	\karollegendenitem{M}{Marke}\karollegendentrenner
	\karollegendenitem{{M>}}{Karol auf Marke}\karollegendentrenner
	\karollegendenitem{Q}{Quader\,/\,Wand}%
}

% ===========================================================================
%  5. Karol-Blöcke  (Blockprogrammierung wie in Robot Karol Online)
% ===========================================================================
%  Verwendung:
%      \begin{karolbloecke}[skalierung=1]
%          \bwiederholemal{5}{%
%              \bbefehl{Schritt}
%              \bbefehl{MarkeSetzen}}
%      \end{karolbloecke}
%
%      \bwiederholesolange{NichtIstWand}{…}
%      \bwenndann{IstZiegel}{…}
%      \bwenndannsonst{IstZiegel}{…}{…}
%      \bwiederholeimmer{…}
%      \bluecke[7em]              — leerer „Geisterblock" zum Ausfüllen
%      \bbedingung{IstZiegel}     — Plättchen, auch im Fließtext nutzbar
%      \bbedingung[2]{IstZiegel}  — mit Zahlenpille (IstZiegel <2>)
%      \bwenndann[2]{IstZiegel}{…}   — dieselbe Zahl im Kontrollblock
% ---------------------------------------------------------------------------
\ExplSyntaxOn

% --- Maße (bei jedem Rendern aus dem aktuellen em berechnet) ---------------
\dim_new:N \l__karol_bnd_dim    % Nasen-/Kerbentiefe
\dim_new:N \l__karol_bnx_dim    % Kerbe: Beginn
\dim_new:N \l__karol_bns_dim    % Kerbe: Schräge
\dim_new:N \l__karol_bnf_dim    % Kerbe: gerades Stück
\dim_new:N \l__karol_bpx_dim    % Innenabstand links/rechts
\dim_new:N \l__karol_bpt_dim    % Innenabstand oben (unter der Kerbe)
\dim_new:N \l__karol_bpb_dim    % Innenabstand unten
\dim_new:N \l__karol_bmw_dim    % Mindestbreite
\dim_new:N \l__karol_barm_dim   % Armbreite der C-Blöcke
\dim_new:N \l__karol_bfut_dim   % Fußhöhe der C-Blöcke
\dim_new:N \l__karol_bmm_dim    % Mindesthöhe eines Mauls
\dim_new:N \l__karol_bbase_dim  % Absenkung für die Grundlinienausrichtung
\tl_new:N  \l__karol_brc_tl     % Eckenradius (für TikZ-Schlüssel)
\dim_new:N \l__karol_brc_dim   % derselbe Wert zum Rechnen
\dim_new:N \l__karol_bkw_dim    % Strichstärke der Blockumrandung
\dim_new:N \l__karol_bschrabs_dim  % Druck: Abstand der Schraffurlinien
\dim_new:N \l__karol_bschrbr_dim   % Druck: Strichstärke der Schraffur
\dim_new:N \l__karol_bschrpad_dim  % Druck: weißer Rand um die Beschriftung
\bool_new:N \l__karol_bschr_bool   % wird dieser Block schraffiert?

% Inline-Modus: Blöcke im Fließtext.  Sie werden in der Schriftgröße des
% umgebenden Textes gesetzt, auf dessen Grundlinie ausgerichtet und mit
% kleineren „Verzierungen" (Kerbe, Nase, Innenabstände) versehen, damit
% ihre Höhe der Zeilenhöhe nahekommt.  Mehrzeilige Blöcke (C-/E-Blöcke)
% erscheinen inline nur mit ihrem Kopf.
\bool_new:N \l__karol_binline_bool   % aktuelle Ausgabe ist ein Inline-Block
\bool_new:N \l__karol_bimbau_bool    % wir stecken bereits in einem Block

\cs_new_protected:Npn \__karol_bdims:
{
	\bool_if:NTF \l__karol_binline_bool
	{	% --- Inline: flachere Kerbe, knappe Innenabstände, keine Mindestbreite
		\dim_set:Nn \l__karol_bnd_dim  { 0.12em }
		\dim_set:Nn \l__karol_bnx_dim  { 0.50em }
		\dim_set:Nn \l__karol_bns_dim  { 0.14em }
		\dim_set:Nn \l__karol_bnf_dim  { 0.45em }
		\dim_set:Nn \l__karol_bpx_dim  { 0.40em }
		\dim_set:Nn \l__karol_bpt_dim  { 0.08em }
		\dim_set:Nn \l__karol_bpb_dim  { 0.10em }
		\dim_zero:N \l__karol_bmw_dim
		\dim_set:Nn \l__karol_barm_dim { 0.70em }
		\dim_set:Nn \l__karol_bfut_dim { 0.40em }
		\dim_set:Nn \l__karol_bmm_dim  { 0.90em }
		\dim_set:Nn \l__karol_brc_dim  { 1.6pt }
		% Schraffur (Druckmodus): inline enger und feiner.  Vom Block
		% bleibt hier nur ein schmaler Saum um den weißen
		% Beschriftungsgrund — bei stapelweitem Abstand lägen darin
		% kaum noch Linien.
		\dim_set:Nn \l__karol_bschrabs_dim { 0.26em }
		\dim_set:Nn \l__karol_bschrbr_dim  { 0.040em }
		\dim_set:Nn \l__karol_bschrpad_dim { 0.12em }
	}
	{
		\dim_set:Nn \l__karol_bnd_dim  { 0.28em }
		\dim_set:Nn \l__karol_bnx_dim  { 0.75em }
		\dim_set:Nn \l__karol_bns_dim  { 0.26em }
		\dim_set:Nn \l__karol_bnf_dim  { 0.80em }
		\dim_set:Nn \l__karol_bpx_dim  { 0.60em }
		\dim_set:Nn \l__karol_bpt_dim  { 0.26em }
		\dim_set:Nn \l__karol_bpb_dim  { 0.30em }
		\dim_set:Nn \l__karol_bmw_dim  { 5.4em }
		\dim_set:Nn \l__karol_barm_dim { 1.05em }
		\dim_set:Nn \l__karol_bfut_dim { 0.62em }
		\dim_set:Nn \l__karol_bmm_dim  { 1.30em }
		\dim_set:Nn \l__karol_brc_dim  { 2pt }
		\dim_set:Nn \l__karol_bschrabs_dim { 0.38em }
		\dim_set:Nn \l__karol_bschrbr_dim  { 0.045em }
		\dim_set:Nn \l__karol_bschrpad_dim { 0.16em }
	}
	\tl_set:Nx \l__karol_brc_tl { \dim_use:N \l__karol_brc_dim }
	% Umrandung: in der Oberfläche 3 px bei 110 px Blockhöhe, also gut
	% 2.7 % — bei den Maßen dieses Pakets rund ein halbes Punkt.  In em,
	% damit sie inline mitschrumpft.
	\dim_set:Nn \l__karol_bkw_dim { 0.045em }
	% Alle Blöcke werden jetzt umrandet, also brauchen ALLE die scharfen
	% Ecken an Kerbe und Nase: dort sind die Segmente kürzer als zwei
	% Eckradien, und pgf macht daraus sonst Spitzen (siehe \bluecke).
	\tl_set:Nn \l__karol_bkerbe_tl { [ sharp~corners ] }
	\tl_set:Nx \l__karol_bkante_tl
		{ [ rounded~corners = \l__karol_brc_tl ] }
}
% Farbe der Blockbeschriftung: weiß auf den kräftigen Flächen der
% Farbfassung, schwarz auf den hellen Graustufen des Druckmodus.
\cs_new_protected:Npn \__karol_bschriftfarbe:
{
	\bool_if:NTF \l__karol_druck_bool
		{ \color{black} } { \color{white} }
}
% Blockschrift: im Stapel \footnotesize, inline die Größe des Fließtextes
% Beschriftungen sind in Robot Karol Online normal gesetzt, nicht fett.
\cs_new_protected:Npn \__karol_blockfont:
{
	\__karol_textfont:
	\bool_if:NF \l__karol_binline_bool { \footnotesize }
}
% Höhenphantom der Beschriftung.  Im Stapel großzügig (Umlaut + Klammer),
% damit alle Blöcke exakt gleich hoch werden; inline knapp (Versalhöhe +
% Unterlänge), damit der Block die Zeilenhöhe nicht unnötig aufbläht.
\cs_new_protected:Npn \__karol_bphantom:
{
	\bool_if:NTF \l__karol_binline_bool
		{ \vphantom { Ag } } { \vphantom { Äg( } }
}
% Flachere Fassung für Bedingungsplättchen und Pillen: sie sitzen INNERHALB
% eines Blocks und dürfen ihn nicht unnötig aufblähen (keine Klammertiefe).
\cs_new_protected:Npn \__karol_bphantom_flach:
{
	\bool_if:NTF \l__karol_binline_bool
		{ \vphantom { Ag } } { \vphantom { Äg } }
}

% Beginn eines Nutzerblocks: Inline-Modus bestimmen und merken, dass wir
% ab jetzt im Blockbau sind (verschachtelte \bbedingung/Pillen erben ihn).
\cs_new_protected:Npn \__karol_bstart:
{
	\bool_if:NF \l__karol_bimbau_bool
	{
		\mode_if_horizontal:TF
			{ \bool_set_true:N  \l__karol_binline_bool }
			{ \bool_set_false:N \l__karol_binline_bool }
	}
	\bool_set_true:N \l__karol_bimbau_bool
}
% Wie \__karol_bstart:, aber erzwingt den Inline-Modus (\i…-Kommandos)
\cs_new_protected:Npn \__karol_bstart_inline:
{
	\bool_if:NF \l__karol_bimbau_bool
		{ \bool_set_true:N \l__karol_binline_bool }
	\bool_set_true:N \l__karol_bimbau_bool
	\mode_leave_vertical:
}

% Block ausgeben: im Stapel als bündige Zeile; inline so abgesenkt, dass
% die Textgrundlinie des Blocks auf der Grundlinie des Fließtextes liegt.
% (Die Bild-Grundlinie liegt an der Blockunterkante, ht = #2, dp = 0; die
% Beschriftung sitzt um Textunterlänge + Fußabstand darüber.)
% #1 = Inhalt, #2 = Gesamthöhen-Variable
\cs_new_protected:Npn \__karol_bemit:nN #1#2
{
	\bool_if:NTF \l__karol_binline_bool
	{
		\mode_leave_vertical:
		\raisebox { -\l__karol_bbase_dim } { \hbox:n { #1 } }
	}
	{ \hbox:n { #1 } }
}

% Arbeitsregister
\box_new:N \l__karol_btxt_box
\box_new:N \l__karol_bbody_box
\box_new:N \l__karol_belse_box
\box_new:N \l__karol_bsonst_box
\dim_new:N \l__karol_bw_dim     % Blockbreite
\dim_new:N \l__karol_bh_dim     % Kopfhöhe
\dim_new:N \l__karol_bm_dim     % Maulhöhe
\dim_new:N \l__karol_bmzwei_dim    % Maulhöhe (sonst-Zweig)
\dim_new:N \l__karol_bs_dim     % Höhe des sonst-Balkens
\dim_new:N \l__karol_bt_dim     % Gesamthöhe
\dim_new:N \l__karol_bbw_dim    % Breite der Gesamt-Boundingbox

% --- Stapel-Umgebung -------------------------------------------------------
% Innerhalb des Stapels: Kästen bündig ohne Zwischenraum übereinander.
\cs_new_protected:Npn \__karol_stapelmodus:
{
	% Im Stapel wird nie inline gesetzt — auch nicht, wenn der umgebende
	% Block inline war.  Damit erben Rümpfe stets die Stapel-Optik.
	\bool_set_false:N \l__karol_binline_bool
	\bool_set_false:N \l__karol_bimbau_bool
	\skip_zero:N \parskip
	\dim_zero:N \parindent
	\skip_set:Nn \baselineskip { 0pt }
	\dim_zero:N \lineskip
	\dim_set:Nn \lineskiplimit { \maxdimen }
}
\tl_new:N \l__karol_bskal_tl
\keys_define:nn { karol / bloecke }
{
	skalierung .tl_set:N  = \l__karol_bskal_tl,
	skalierung .initial:n = { 1 },
}
\box_new:N \l__karol_bstapel_box
\NewDocumentEnvironment { karolbloecke } { O{} }
{
	\keys_set:nn { karol / bloecke } { #1 }
	\vbox_set:Nw \l__karol_bstapel_box
	\__karol_stapelmodus:
}
{
	\vbox_set_end:
	\mode_leave_vertical:
	\hbox:n
	{
		\exp_args:NV \scalebox \l__karol_bskal_tl
			{ \box_use:N \l__karol_bstapel_box }
	}
}

% --- Kerbenpfade -----------------------------------------------------------
% Eckenmodus an Kerbe und Nase.  Gefüllte Blöcke zeichnen die Kontur nicht
% (draw=none), dort bleiben beide Marken leer und der Pfad ist unverändert.
% Der Geisterblock \bluecke STRICHELT die Kontur — und dort sind die
% Kerbensegmente kürzer als zwei Eckradien: inline ist die Schräge rund
% 2.0pt lang, zwei Radien à 1.6pt brauchen 3.2pt.  pgf erzeugt daraus einen
% entarteten Bogen, der als Spitze aus der Kontur schießt.  Deshalb setzt
% \bluecke an Kerbe und Nase auf scharfe Ecken und rundet nur die vier
% Außenecken.
\tl_new:N \l__karol_bkerbe_tl   % vor der Kerbe/Nase einzufügen
\tl_new:N \l__karol_bkante_tl   % danach wieder einzufügen
\dim_new:N \l__karol_bstr_dim   % Geisterblock: Strichlänge
\dim_new:N \l__karol_bluk_dim   % Geisterblock: Lückenlänge
\tl_new:N  \l__karol_bluf_tl    % Geisterblock: Füllfarbe

% Obere Kante mit Kerbe, von (0,0) bis (W,0).  Der Rumpf ohne den
% Startpunkt wird auch von der hellen Kante gebraucht, die schon an der
% linken Seite beginnt.
\cs_new:Npn \__karol_pfad_oben:
	{ (0pt, 0pt) \__karol_pfad_oben_ab: }
\cs_new:Npn \__karol_pfad_oben_ab:
{
	\l__karol_bkerbe_tl
	-- ( \dim_use:N \l__karol_bnx_dim , 0pt )
	-- ( \dim_eval:n { \l__karol_bnx_dim + \l__karol_bns_dim },
	     -\dim_use:N \l__karol_bnd_dim )
	-- ( \dim_eval:n { \l__karol_bnx_dim + \l__karol_bns_dim + \l__karol_bnf_dim },
	     -\dim_use:N \l__karol_bnd_dim )
	-- ( \dim_eval:n { \l__karol_bnx_dim + 2\l__karol_bns_dim + \l__karol_bnf_dim },
	     0pt )
	\l__karol_bkante_tl
	-- ( \dim_use:N \l__karol_bw_dim , 0pt )
}
% Untere Kante mit Nase, von (W,-#1) zurück bis (0,-#1):
\cs_new:Npn \__karol_pfad_unten:n #1
{
	-- ( \dim_use:N \l__karol_bw_dim , \dim_eval:n { 0pt-(#1) } )
	\l__karol_bkerbe_tl
	-- ( \dim_eval:n { \l__karol_bnx_dim + 2\l__karol_bns_dim + \l__karol_bnf_dim },
	     \dim_eval:n { 0pt-(#1) } )
	-- ( \dim_eval:n { \l__karol_bnx_dim + \l__karol_bns_dim + \l__karol_bnf_dim },
	     \dim_eval:n { 0pt-(#1) - \l__karol_bnd_dim } )
	-- ( \dim_eval:n { \l__karol_bnx_dim + \l__karol_bns_dim },
	     \dim_eval:n { 0pt-(#1) - \l__karol_bnd_dim } )
	-- ( \dim_use:N \l__karol_bnx_dim , \dim_eval:n { 0pt-(#1) } )
	\l__karol_bkante_tl
	-- ( 0pt , \dim_eval:n { 0pt-(#1) } )
	-- cycle
}
% Maulöffnung an der Kopf-/Balkenunterkante bei y = -#1
% (von rechts nach links; Nase zeigt ins Maul):
\cs_new:Npn \__karol_pfad_maul_auf:n #1
{
	-- ( \dim_use:N \l__karol_bw_dim , \dim_eval:n { 0pt-(#1) } )
	\l__karol_bkerbe_tl
	-- ( \dim_eval:n { \l__karol_barm_dim + \l__karol_bnx_dim
	                   + 2\l__karol_bns_dim + \l__karol_bnf_dim },
	     \dim_eval:n { 0pt-(#1) } )
	-- ( \dim_eval:n { \l__karol_barm_dim + \l__karol_bnx_dim
	                   + \l__karol_bns_dim + \l__karol_bnf_dim },
	     \dim_eval:n { 0pt-(#1) - \l__karol_bnd_dim } )
	-- ( \dim_eval:n { \l__karol_barm_dim + \l__karol_bnx_dim
	                   + \l__karol_bns_dim },
	     \dim_eval:n { 0pt-(#1) - \l__karol_bnd_dim } )
	-- ( \dim_eval:n { \l__karol_barm_dim + \l__karol_bnx_dim },
	     \dim_eval:n { 0pt-(#1) } )
	\l__karol_bkante_tl
	-- ( \dim_use:N \l__karol_barm_dim , \dim_eval:n { 0pt-(#1) } )
}
% Maulschluss an der Fuß-/Balkenoberkante bei y = -#1
% (von links nach rechts; Kerbe nimmt die Nase des letzten Blocks auf):
\cs_new:Npn \__karol_pfad_maul_zu:n #1
{
	-- ( \dim_use:N \l__karol_barm_dim , \dim_eval:n { 0pt-(#1) } )
	\l__karol_bkerbe_tl
	-- ( \dim_eval:n { \l__karol_barm_dim + \l__karol_bnx_dim },
	     \dim_eval:n { 0pt-(#1) } )
	-- ( \dim_eval:n { \l__karol_barm_dim + \l__karol_bnx_dim
	                   + \l__karol_bns_dim },
	     \dim_eval:n { 0pt-(#1) - \l__karol_bnd_dim } )
	-- ( \dim_eval:n { \l__karol_barm_dim + \l__karol_bnx_dim
	                   + \l__karol_bns_dim + \l__karol_bnf_dim },
	     \dim_eval:n { 0pt-(#1) - \l__karol_bnd_dim } )
	-- ( \dim_eval:n { \l__karol_barm_dim + \l__karol_bnx_dim
	                   + 2\l__karol_bns_dim + \l__karol_bnf_dim },
	     \dim_eval:n { 0pt-(#1) } )
	\l__karol_bkante_tl
	-- ( \dim_use:N \l__karol_bw_dim , \dim_eval:n { 0pt-(#1) } )
}

% --- Kopftext ausmessen, Breite/Kopfhöhe setzen ----------------------------
\cs_new_protected:Npn \__karol_bmess:n #1
{
	\__karol_bdims:
	\hbox_set:Nn \l__karol_btxt_box
		{ \__karol_blockfont: \__karol_bschriftfarbe: \__karol_bphantom: #1 }
	\dim_set:Nn \l__karol_bw_dim
		{ \box_wd:N \l__karol_btxt_box + 2 \l__karol_bpx_dim }
	\dim_compare:nT { \l__karol_bw_dim < \l__karol_bmw_dim }
		{ \dim_set_eq:NN \l__karol_bw_dim \l__karol_bmw_dim }
	\dim_set:Nn \l__karol_bh_dim
	{
		\l__karol_bnd_dim + \l__karol_bpt_dim
		+ \box_ht:N \l__karol_btxt_box + \box_dp:N \l__karol_btxt_box
		+ \l__karol_bpb_dim
	}
	\__karol_bbase_setzen:
}
% Abstand von der Unterkante der Boundingbox (= Bild-Grundlinie) hinauf zur
% Textgrundlinie im Block.  \raisebox{-\l__karol_bbase_dim} legt beide
% Grundlinien im Fließtext exakt übereinander.  Inline zählt die Nase zur
% Boundingbox (siehe \__karol_bbbox:N) und damit auch hier mit.
\cs_new_protected:Npn \__karol_bbase_setzen:
{
	\dim_set:Nn \l__karol_bbase_dim
		{ \box_dp:N \l__karol_btxt_box + \l__karol_bpb_dim }
	\bool_if:NT \l__karol_binline_bool
		{ \dim_add:Nn \l__karol_bbase_dim { \l__karol_bnd_dim } }
}
% Untere Kante der Boundingbox eines Blocks der Höhe #1.  Im Stapel endet
% sie an der Blockunterkante (die Nase ragt heraus und greift in den
% Folgeblock); inline zählt die Nase mit, damit TeX den Zeilenabstand
% richtig bemisst und nichts in die nächste Zeile ragt.
\cs_new:Npn \__karol_bbbox:N #1
{
	\bool_if:NTF \l__karol_binline_bool
		{ \dim_eval:n { 0pt - #1 - \l__karol_bnd_dim } }
		{ \dim_eval:n { 0pt - #1 } }
}
% Kopftext in den Block setzen (Grundlinie unterhalb der Kerbe)
% Achtung: die Box MUSS vor dem tikzpicture gefüllt werden (nullfont!)
\cs_new_protected:Npn \__karol_btxt_setzen:n #1
	{ \__karol_btext_at:nN {#1} \l__karol_btxt_box }
\cs_new_protected:Npn \__karol_btext_at:nN #1#2  % #1 = y-Offset, #2 = Box
{
	\bool_if:NTF \l__karol_bschr_bool
	{
		% Schraffierter Block: die Beschriftung bekommt weißen Grund,
		% sonst laufen die Schraffurlinien durch die Buchstaben.  Nur der
		% HINTERGRUND ist schraffiert — Beschriftung und Namenspille
		% stehen frei.  Der Knoten wird um seinen Innenabstand nach links
		% versetzt, damit der Text auf derselben Stelle sitzt wie ohne
		% Schraffur (anchor=base~west misst ab der Knotenkante, und die
		% liegt um inner~xsep vor dem Text).
		\node [ anchor=base~west, fill=white,
		        inner~xsep=\dim_use:N \l__karol_bschrpad_dim,
		        inner~ysep=\dim_eval:n { 0.3 \l__karol_bschrpad_dim } ]
			at ( \dim_eval:n { \l__karol_bpx_dim - \l__karol_bschrpad_dim } ,
			     \dim_eval:n { 0pt-(#1) - \l__karol_bnd_dim - \l__karol_bpt_dim
			                   - \box_ht:N #2 } )
			{ \box_use:N #2 };
	}
	{
		\node [ anchor=base~west, inner~sep=0pt ]
			at ( \dim_use:N \l__karol_bpx_dim ,
			     \dim_eval:n { 0pt-(#1) - \l__karol_bnd_dim - \l__karol_bpt_dim
			                   - \box_ht:N #2 } )
			{ \box_use:N #2 };
	}
}

% --- Blockfläche mit Anschliff ---------------------------------------------
% Robot Karol Online setzt einen gewöhnlichen 3D-Anschliff ein: jede nach
% oben oder links zeigende Kante ist aufgehellt, jede nach unten oder
% rechts zeigende abgedunkelt.  Wichtig daran ist vor allem, dass
% gleichfarbige Blöcke voneinander abgesetzt sind — ein grüner wenn-Block
% in einer grünen Schleife wäre sonst nicht zu erkennen.
%
% Statt die Kontur in helle und dunkle Teilstücke zu zerlegen (fehler-
% anfällig an Ecken, Kerbe, Nase und Maul), wird der Effekt so erzeugt,
% wie er physikalisch entsteht:
%
%   1. Auf die Fläche BESCHNEIDEN.  Dadurch liegt jeder Strich vollständig
%      INNERHALB des Blocks — Blöcke können sich nicht mehr überlagern und
%      passen exakt ineinander.
%   2. Den kompletten Rand hell zeichnen.
%   3. Denselben Rand dunkel darüber, um eine Strichstärke nach links oben
%      versetzt: an nach unten und rechts zeigenden Kanten rutscht er ins
%      Innere und deckt das Helle ab, an nach oben und links zeigenden aus
%      der Fläche heraus, wo der Beschnitt ihn entfernt.  Schräge Kanten
%      bekommen automatisch den passenden Zwischenwert.
%
% Weil beide Striche denselben Pfad benutzen, folgen sie der Blockform in
% jedem Detail — Rundungen, Kerbe, Nase und Maulöffnungen inbegriffen.
% Die doppelte Strichstärke ist Absicht: der Beschnitt halbiert sie.
%   #1 = Blockfarbe, #2 = geschlossener Pfad der Blockform
\cs_new_protected:Npn \__karol_brand:nn #1#2
{
	% Einmal festhalten, ob dieser Block schraffiert wird: die
	% Beschriftung braucht die Antwort gleich noch einmal.  Der Aufruf
	% steht unmittelbar vor \__karol_btxt_setzen:n, verschachtelte
	% Plättchen und Rümpfe sind zu diesem Zeitpunkt längst gesetzt.
	\bool_set_false:N \l__karol_bschr_bool
	\bool_if:NT \l__karol_druck_bool
	{
		\clist_if_in:NnT \c__karol_bschraffur_clist {#1}
			{ \bool_set_true:N \l__karol_bschr_bool }
	}
	\bool_if:NTF \l__karol_druck_bool
		{ \__karol_brand_druck:nn {#1} {#2} }
		{ \__karol_brand_farbe:nn {#1} {#2} }
}
% Druckmodus: an die Stelle des Anschliffs tritt eine schwarze Kontur.
% Die Blockfarben sind hier helle Graustufen — aus ihnen ließe sich kein
% sichtbarer Hell-Dunkel-Verlauf mehr gewinnen, wohl aber verlöre ein
% weißer Block ohne Kontur jede Form.  Beschnitten wird wie beim
% Anschliff, damit die Kontur ganz INNERHALB des Blocks liegt und
% gestapelte Blöcke sich nicht gegenseitig überzeichnen; die doppelte
% Strichstärke gleicht die Halbierung durch den Beschnitt aus.
%
% Die Fläche wird hier NICHT nach außen überzeichnet (in der Farbfassung
% 0.1pt gegen den Antialiasing-Saum): eine weiße Blockfläche würde sonst
% die Kontur des darüberliegenden Blocks anknabbern.  Nötig ist die
% Überzeichnung im Druck auch nicht — die Kontur deckt die Fuge ab.
% Bloecke, deren Farbe in \c__karol_bschraffur_clist steht, bekommen
% statt eines Grauwertes eine Schraffur unter 45 Grad (von links unten
% nach rechts oben).  Grund: \bbefehl und \baufruf/\banweisung haben
% dieselbe Form, im Original unterscheidet sie nur die Farbe (violett
% gegen rot) — ein bloss etwas dunkleres Grau trug diesen Unterschied
% nicht.  Die Schraffur liegt auf weissem Grund und braucht dabei
% weniger Farbe als eine graue Flaeche.  Die Pille mit dem Namen wird
% spaeter darueber gesetzt und deckt sie dort ab; ihre Flaeche bleibt
% also frei, wie es sein soll.
\cs_new_protected:Npn \__karol_brand_druck:nn #1#2
{
	\fill [ #1, line~join = round, rounded~corners = \l__karol_brc_tl ]
		#2 ;
	\bool_if:NT \l__karol_bschr_bool
	{
		\fill [ rounded~corners = \l__karol_brc_tl ,
		        pattern~color = karolDruckSchraffur ,
		        pattern = {
		          Lines [ angle = 45 ,
		                  distance = \dim_use:N \l__karol_bschrabs_dim ,
		                  line~width = \dim_use:N \l__karol_bschrbr_dim ] } ]
			#2 ;
	}
	\begin{scope}
		\clip [ rounded~corners = \l__karol_brc_tl ] #2 ;
		\draw [ draw = karolDruckKontur,
		        line~width = \dim_eval:n { 2.6 \l__karol_bkw_dim },
		        line~join = round, rounded~corners = \l__karol_brc_tl ]
			#2 ;
	\end{scope}
}
\cs_new_protected:Npn \__karol_brand_farbe:nn #1#2
{
	% Die Fläche einen Hauch überzeichnen (0.1pt nach außen).  Zwei exakt
	% aneinanderstoßende Flächen hinterlassen sonst einen hellen
	% Antialiasing-Saum — sichtbar dort, wo die Nase eines Blocks in der
	% Kerbe des nächsten sitzt.  Der Anschliff bleibt davon unberührt, er
	% wird auf den ungewachsenen Pfad beschnitten.
	\draw [ fill = #1, draw = #1, line~width = 0.2pt,
	        line~join = round, rounded~corners = \l__karol_brc_tl ]
		#2 ;
	\begin{scope}
		\clip [ rounded~corners = \l__karol_brc_tl ] #2 ;
		\draw [ fill = #1, draw = #1!70!white,
		        line~width = \dim_eval:n { 2 \l__karol_bkw_dim },
		        line~join = round, rounded~corners = \l__karol_brc_tl ]
			#2 ;
		\draw [ draw = #1!80!black,
		        line~width = \dim_eval:n { 2 \l__karol_bkw_dim },
		        line~join = round, rounded~corners = \l__karol_brc_tl,
		        xshift = -\dim_use:N \l__karol_bkw_dim,
		        yshift = \dim_use:N \l__karol_bkw_dim ]
			#2 ;
	\end{scope}
}

% --- Einfacher Anweisungsblock ---------------------------------------------
% #1 = Farbname   #2 = Beschriftung
\cs_new_protected:Npn \__karol_bblock:nn #1#2
{
	\tl_set:Nn \l__karol_bfarbe_tl {#1}
	\__karol_bmess:n {#2}
	\__karol_bemit:nN
	{
		\begin{tikzpicture}
			\useasboundingbox (0pt, 0pt) rectangle
				( \dim_use:N \l__karol_bw_dim ,
				  \__karol_bbbox:N \l__karol_bh_dim );
			\__karol_brand:nn {#1}
				{ \__karol_pfad_oben:
				  \__karol_pfad_unten:n \l__karol_bh_dim }
			\__karol_btxt_setzen:n { 0pt }
		\end{tikzpicture}
	}
	\l__karol_bh_dim
}

% --- C-Block (eine Maulöffnung) --------------------------------------------
% #1 = Farbname   #2 = Kopfbeschriftung   #3 = Rumpf
\cs_new_protected:Npn \__karol_bcblock:nnn #1#2#3
{
	% Inline gibt es keine Maulöffnung: nur der Kopf wird gesetzt.
	\bool_if:NTF \l__karol_binline_bool
		{ \__karol_bblock:nn      {#1} {#2}      }
		{ \__karol_bcblock_voll:nnn {#1} {#2} {#3} }
}
\cs_new_protected:Npn \__karol_bcblock_voll:nnn #1#2#3
{
	\tl_set:Nn \l__karol_bfarbe_tl {#1}
	\vbox_set:Nn \l__karol_bbody_box { \__karol_stapelmodus: #3 }
	\__karol_bmess:n {#2}
	\dim_set:Nn \l__karol_bm_dim
		{ \box_ht:N \l__karol_bbody_box + \box_dp:N \l__karol_bbody_box }
	\dim_compare:nT { \l__karol_bm_dim < \l__karol_bmm_dim }
		{ \dim_set_eq:NN \l__karol_bm_dim \l__karol_bmm_dim }
	\dim_set:Nn \l__karol_bt_dim
		{ \l__karol_bh_dim + \l__karol_bm_dim + \l__karol_bfut_dim }
	\dim_set:Nn \l__karol_bbw_dim
		{ \l__karol_barm_dim + \box_wd:N \l__karol_bbody_box + 0.4em }
	\dim_compare:nT { \l__karol_bbw_dim < \l__karol_bw_dim }
		{ \dim_set_eq:NN \l__karol_bbw_dim \l__karol_bw_dim }
	\__karol_bemit:nN
	{
		\begin{tikzpicture}
			\useasboundingbox (0pt, 0pt) rectangle
				( \dim_use:N \l__karol_bbw_dim , -\dim_use:N \l__karol_bt_dim );
			\__karol_brand:nn {#1}
			{
				\__karol_pfad_oben:
				\__karol_pfad_maul_auf:n \l__karol_bh_dim
				-- ( \dim_use:N \l__karol_barm_dim ,
				     \dim_eval:n { - \l__karol_bh_dim - \l__karol_bm_dim } )
				\__karol_pfad_maul_zu:n
					{ \dim_eval:n { \l__karol_bh_dim + \l__karol_bm_dim } }
				\__karol_pfad_unten:n \l__karol_bt_dim
			}
			\__karol_btxt_setzen:n { 0pt }
			\node [ anchor=north~west, inner~sep=0pt ]
				at ( \dim_use:N \l__karol_barm_dim ,
				     -\dim_use:N \l__karol_bh_dim )
				{ \box_use:N \l__karol_bbody_box };
		\end{tikzpicture}
	}
	\l__karol_bt_dim
}

% Mittelteil des E-Blocks (beide Mäuler und der sonst-Balken).  Fläche und
% dunkle Kante brauchen ihn gleichermaßen.
\cs_new:Npn \__karol_beblock_rumpf:
{
	\__karol_pfad_maul_auf:n \l__karol_bh_dim
	-- ( \dim_use:N \l__karol_barm_dim ,
	     \dim_eval:n { - \l__karol_bh_dim - \l__karol_bm_dim } )
	\__karol_pfad_maul_zu:n
		{ \dim_eval:n { \l__karol_bh_dim + \l__karol_bm_dim } }
	\__karol_pfad_maul_auf:n
		{ \dim_eval:n { \l__karol_bh_dim + \l__karol_bm_dim
		                + \l__karol_bs_dim } }
	-- ( \dim_use:N \l__karol_barm_dim ,
	     \dim_eval:n { - \l__karol_bh_dim - \l__karol_bm_dim
	                   - \l__karol_bs_dim - \l__karol_bmzwei_dim } )
	\__karol_pfad_maul_zu:n
		{ \dim_eval:n { \l__karol_bh_dim + \l__karol_bm_dim
		                + \l__karol_bs_dim + \l__karol_bmzwei_dim } }
}

% --- E-Block (wenn … dann … sonst …) ---------------------------------------
% #1 = Farbname  #2 = Kopf  #3 = dann-Rumpf  #4 = sonst-Rumpf
\cs_new_protected:Npn \__karol_beblock:nnnn #1#2#3#4
{
	% Inline gibt es keine Maulöffnungen: nur der Kopf wird gesetzt.
	\bool_if:NTF \l__karol_binline_bool
		{ \__karol_bblock:nn        {#1} {#2}           }
		{ \__karol_beblock_voll:nnnn {#1} {#2} {#3} {#4} }
}
\cs_new_protected:Npn \__karol_beblock_voll:nnnn #1#2#3#4
{
	\tl_set:Nn \l__karol_bfarbe_tl {#1}
	\vbox_set:Nn \l__karol_bbody_box { \__karol_stapelmodus: #3 }
	\vbox_set:Nn \l__karol_belse_box { \__karol_stapelmodus: #4 }
	\__karol_bmess:n {#2}
	% Höhe des sonst-Balkens aus seiner Beschriftung
	\hbox_set:Nn \l__karol_bsonst_box
		{ \__karol_blockfont: \__karol_bschriftfarbe: \__karol_bphantom: sonst }
	\dim_set:Nn \l__karol_bs_dim
	{
		\l__karol_bnd_dim + \l__karol_bpt_dim
		+ \box_ht:N \l__karol_bsonst_box + \box_dp:N \l__karol_bsonst_box
		+ \l__karol_bpb_dim
	}
	\dim_set:Nn \l__karol_bm_dim
		{ \box_ht:N \l__karol_bbody_box + \box_dp:N \l__karol_bbody_box }
	\dim_compare:nT { \l__karol_bm_dim < \l__karol_bmm_dim }
		{ \dim_set_eq:NN \l__karol_bm_dim \l__karol_bmm_dim }
	\dim_set:Nn \l__karol_bmzwei_dim
		{ \box_ht:N \l__karol_belse_box + \box_dp:N \l__karol_belse_box }
	\dim_compare:nT { \l__karol_bmzwei_dim < \l__karol_bmm_dim }
		{ \dim_set_eq:NN \l__karol_bmzwei_dim \l__karol_bmm_dim }
	\dim_set:Nn \l__karol_bt_dim
	{
		\l__karol_bh_dim + \l__karol_bm_dim + \l__karol_bs_dim
		+ \l__karol_bmzwei_dim + \l__karol_bfut_dim
	}
	\dim_set:Nn \l__karol_bbw_dim
		{ \l__karol_barm_dim + \box_wd:N \l__karol_bbody_box + 0.4em }
	\dim_compare:nT { \l__karol_bbw_dim < \l__karol_bw_dim }
		{ \dim_set_eq:NN \l__karol_bbw_dim \l__karol_bw_dim }
	\dim_compare:nT
		{ \l__karol_bbw_dim < \l__karol_barm_dim + \box_wd:N \l__karol_belse_box }
		{ \dim_set:Nn \l__karol_bbw_dim
			{ \l__karol_barm_dim + \box_wd:N \l__karol_belse_box + 0.4em } }
	\__karol_bemit:nN
	{
		\begin{tikzpicture}
			\useasboundingbox (0pt, 0pt) rectangle
				( \dim_use:N \l__karol_bbw_dim , -\dim_use:N \l__karol_bt_dim );
			\__karol_brand:nn {#1}
			{
				\__karol_pfad_oben:
				\__karol_beblock_rumpf:
				\__karol_pfad_unten:n \l__karol_bt_dim
			}
			\__karol_btxt_setzen:n { 0pt }
			% sonst-Beschriftung (Box wurde VOR dem Bild gefüllt)
			\__karol_btext_at:nN
				{ \dim_eval:n { \l__karol_bh_dim + \l__karol_bm_dim } }
				\l__karol_bsonst_box
			% Rümpfe
			\node [ anchor=north~west, inner~sep=0pt ]
				at ( \dim_use:N \l__karol_barm_dim ,
				     -\dim_use:N \l__karol_bh_dim )
				{ \box_use:N \l__karol_bbody_box };
			\node [ anchor=north~west, inner~sep=0pt ]
				at ( \dim_use:N \l__karol_barm_dim ,
				     \dim_eval:n { - \l__karol_bh_dim - \l__karol_bm_dim
				                   - \l__karol_bs_dim } )
				{ \box_use:N \l__karol_belse_box };
		\end{tikzpicture}
	}
	\l__karol_bt_dim
}

% --- Eingabefelder: Zahl (Pille) und Bedingung (Sechseck) ------------------
% Aktuelle Blockfarbe (für Pillen-Tönung), von den Buildern gesetzt
\tl_new:N \l__karol_bfarbe_tl
\tl_set:Nn \l__karol_bfarbe_tl { karolBlockBefehl }
% Pille: blasse Tönung der Blockfarbe, dunkle Schrift (wie Karol Online)
\dim_new:N \l__karol_bpxs_dim   % Pille: Innenabstand waagerecht
\dim_new:N \l__karol_bpys_dim   % Pille: Innenabstand senkrecht
\dim_new:N \l__karol_bpmw_dim   % Pille: Mindestbreite
\box_new:N \l__karol_bptxt_box  % Pillenbeschriftung
\box_new:N \l__karol_bpinh_box  % Pilleninhalt (Beschriftung + Platz fürs Dreieck)
\dim_new:N \l__karol_bph_dim    % Pillenhöhe
\dim_new:N \l__karol_bptri_dim  % Auswahldreieck: Seitenlänge
\dim_new:N \l__karol_bpgap_dim  % Auswahldreieck: Lücke zum Text
\dim_new:N \l__karol_bpby_dim   % Auswahldreieck: Spitze über der Grundlinie
\tl_new:N  \l__karol_bpdreieck_tl  % Farbe des Auswahldreiecks

% Verhältnisse des Auswahldreiecks, gemessen an der Oberfläche von Robot
% Karol Online (Pillenhöhe H = 83 px):  Seitenlänge 27 px = 0.325 H,
% Lücke Text->Dreieck 22 px = 0.265 H, und die Spitze steht praktisch auf
% der Textgrundlinie (2 px darüber = 0.024 H).  Das Dreieck hat die VOLLE
% Blockfarbe, nicht die aufgehellte der Pille.
\fp_const:Nn \c__karol_btri_fp     { 0.325 }
% 0.265 ist im Original der Abstand von der TINTE des letzten Buchstabens;
% hier wird ab der Textbox gemessen, deren rechte Kante die Seitenbreite
% des Glyphen schon enthält (~0.09 H).  Deshalb der kleinere Wert.
\fp_const:Nn \c__karol_btrigap_fp  { 0.19 }
\fp_const:Nn \c__karol_btribas_fp  { 0.024 }

% #1 = Auswahldreieck ja/nein (Boolean-Ausdruck), #2 = Beschriftung
\cs_new_protected:Npn \__karol_bpille_aux:nn #1#2
{
	\bool_if:NTF \l__karol_binline_bool
	{
		\dim_set:Nn \l__karol_bpxs_dim { 0.22em }
		\dim_set:Nn \l__karol_bpys_dim { 0.08em }
		\dim_set:Nn \l__karol_bpmw_dim { 0.85em }
	}
	{
		\dim_set:Nn \l__karol_bpxs_dim { 0.26em }
		\dim_set:Nn \l__karol_bpys_dim { 0.14em }
		\dim_set:Nn \l__karol_bpmw_dim { 1.05em }
	}
	% Beschriftung VOR dem Bild setzen (im tikzpicture gilt \nullfont) und
	% daraus die Pillenhöhe bestimmen; em-Maße müssen ohnehin vorher in
	% Register, im Bild sind sie 0pt.
	\hbox_set:Nn \l__karol_bptxt_box
	{
		\normalfont \__karol_textfont: \color{black}
		\bool_if:NF \l__karol_binline_bool { \footnotesize }
		\__karol_bphantom_flach: #2
	}
	\dim_set:Nn \l__karol_bph_dim
	{
		\box_ht:N \l__karol_bptxt_box + \box_dp:N \l__karol_bptxt_box
		+ 2 \l__karol_bpys_dim
	}
	\bool_if:nTF { #1 }
	{
		\dim_set:Nn \l__karol_bptri_dim
			{ \fp_to_dim:n { \c__karol_btri_fp * \l__karol_bph_dim } }
		\dim_set:Nn \l__karol_bpgap_dim
			{ \fp_to_dim:n { \c__karol_btrigap_fp * \l__karol_bph_dim } }
		\dim_set:Nn \l__karol_bpby_dim
			{ \fp_to_dim:n { \c__karol_btribas_fp * \l__karol_bph_dim } }
	}
	{
		\dim_zero:N \l__karol_bptri_dim
		\dim_zero:N \l__karol_bpgap_dim
		\dim_zero:N \l__karol_bpby_dim
	}
	% Im Farbmodus hebt sich die Pille durch ihre Tönung vom Block ab und
	% braucht keinen Rand.  Im Druck ist sie auf einem weißen \bbefehl
	% selbst weiß — dort trägt erst die Kontur die Information, dass hier
	% ein Eingabefeld sitzt.  Das Auswahldreieck hätte aus demselben Grund
	% keine Farbe mehr und bekommt einen festen Grauwert.
	% Das Auswahldreieck hat im Druck keine Blockfarbe mehr (der \bbefehl
	% ist dort weiß) und bekommt einen festen Grauwert.
	\tl_set_eq:NN \l__karol_bpdreieck_tl \l__karol_bfarbe_tl
	\bool_if:NT \l__karol_druck_bool
		{ \tl_set:Nn \l__karol_bpdreieck_tl { karolDruckAkzent } }
	% Platz für Lücke und Dreieck FEST in eine Box backen.  Als Leerraum
	% am Ende des Knotentextes geht er verloren — TikZ verwirft ihn, die
	% Pille wäre dann genau um die Dreieckbreite zu schmal.  Die Breite
	% einer gesetzten Box kann TikZ dagegen nicht mehr ändern.
	\hbox_set:Nn \l__karol_bpinh_box
	{
		\box_use:N \l__karol_bptxt_box
		\skip_horizontal:n
			{ \l__karol_bpgap_dim + \l__karol_bptri_dim }
	}
	\hbox:n
	{
		\begin{tikzpicture} [ baseline=0pt ]
			\node (karolpille) [ fill=\l__karol_bfarbe_tl!40!white,
			        karolpillenrand,
			        rounded~corners=\l__karol_brc_tl,
			        inner~xsep=\l__karol_bpxs_dim,
			        inner~ysep=\l__karol_bpys_dim,
			        minimum~width=\l__karol_bpmw_dim,
			        anchor=base ]
				at (0pt, 0pt)
				{ \box_use:N \l__karol_bpinh_box };
			% Auswahldreieck, rechtsbündig im Innenraum der Pille.
			% Bezugspunkt ist der Anker "base east": rechte Knotenkante auf
			% Höhe der Textgrundlinie — damit stimmt die Lage auch dann,
			% wenn die Mindestbreite die Pille breiter macht als ihr Inhalt.
			\dim_compare:nT { \l__karol_bptri_dim > 0pt }
			{
				\begin{scope} [ shift={ (karolpille.base~east) } ]
					\fill [ \l__karol_bpdreieck_tl ]
						( \dim_eval:n
						    { 0pt - \l__karol_bpxs_dim - \l__karol_bptri_dim },
						  \dim_eval:n
						    { \l__karol_bpby_dim + \l__karol_bptri_dim } )
						-- ( \dim_eval:n { 0pt - \l__karol_bpxs_dim },
						     \dim_eval:n
						       { \l__karol_bpby_dim + \l__karol_bptri_dim } )
						-- ( \dim_eval:n { 0pt - \l__karol_bpxs_dim
						                   - 0.5 \l__karol_bptri_dim },
						     \dim_use:N \l__karol_bpby_dim )
						-- cycle;
				\end{scope}
			}
		\end{tikzpicture}
	}
}
% Pille ohne Auswahl (Zahlen, Namen von Anweisungsdefinitionen, Kommentare)
\cs_new_protected:Npn \__karol_bpille:n #1
	{ \__karol_bpille_aux:nn { \c_false_bool } {#1} }
% Pille mit Auswahldreieck (Aufrufe, Himmelsrichtungen)
\cs_new_protected:Npn \__karol_bpille_menue:n #1
	{ \__karol_bpille_aux:nn { \c_true_bool } {#1} }
\box_new:N \l__karol_bhex_box
\dim_new:N \l__karol_bhw_dim
\dim_new:N \l__karol_bho_dim
\dim_new:N \l__karol_bhu_dim
\dim_new:N \l__karol_bhp_dim
\dim_new:N \l__karol_bhn_dim
\dim_new:N \l__karol_bhm_dim
\dim_new:N \l__karol_bhk_dim
% \bbedingung[Anzahl]{Bedingung} — die optionale Zahl erscheint wie in
% Robot Karol Online als Pille hinter der Beschriftung: IstZiegel <2>
% bzw. NichtIstZiegel <2> (dort ein Zahlenfeld ohne Auswahldreieck,
% siehe referenz-karol-online/ibedNummer.png).
\NewDocumentCommand { \bbedingung } { O{} m }
{
	\group_begin:
	\__karol_farbschema:
	\__karol_bstart:
	\__karol_bdims:
	\tl_if_empty:nTF {#2}
	{	% Leere Fassung: weißer Sockel (für Lückenaufgaben)
		\hbox_set:Nn \l__karol_bhex_box
			{ \__karol_blockfont: \__karol_bphantom_flach: \hspace{1.1em} }
		\__karol_bbedform:n { white }
	}
	{
		\hbox_set:Nn \l__karol_bhex_box
		{
			\__karol_blockfont: \__karol_bschriftfarbe: \__karol_bphantom_flach:
			#2
			\tl_if_empty:nF {#1}
				{ ~ \__karol_bzahlpille:n {#1} }
		}
		\__karol_bbedform:n { karolBlockBedingung }
	}
	\group_end:
}
% Zahlenpille eines Bedingungsplättchens.  Die Farbe muss hier gesetzt
% werden: innerhalb von \bwenndann oder \bwiederholesolange steht in
% \l__karol_bfarbe_tl die Farbe des UMGEBENDEN Blocks, die Pille bekäme
% sonst dessen Grün statt des Bedingungsblaus.
\cs_new_protected:Npn \__karol_bzahlpille:n #1
{
	\group_begin:
		\tl_set:Nn \l__karol_bfarbe_tl { karolBlockBedingung }
		\__karol_bpille:n {#1}
	\group_end:
}
% Form: abgerundetes Rechteck, Stecker an der linken Kante (Karol Online)
\cs_new_protected:Npn \__karol_bbedform:n #1
{
	\bool_if:NTF \l__karol_binline_bool
	{
		\dim_set:Nn \l__karol_bhw_dim
			{ \box_wd:N \l__karol_bhex_box + 0.34em }
		\dim_set:Nn \l__karol_bho_dim
			{ \box_ht:N \l__karol_bhex_box + 0.10em }
		\dim_set:Nn \l__karol_bhu_dim
			{ \box_dp:N \l__karol_bhex_box + 0.10em }
		\dim_set:Nn \l__karol_bhp_dim { 0.20em }   % Steckertiefe
		\dim_set:Nn \l__karol_bhn_dim { 0.17em }   % halbe Steckerhöhe
	}
	{
		\dim_set:Nn \l__karol_bhw_dim
			{ \box_wd:N \l__karol_bhex_box + 0.52em }
		\dim_set:Nn \l__karol_bho_dim
			{ \box_ht:N \l__karol_bhex_box + 0.17em }
		\dim_set:Nn \l__karol_bhu_dim
			{ \box_dp:N \l__karol_bhex_box + 0.17em }
		\dim_set:Nn \l__karol_bhp_dim { 0.30em }   % Steckertiefe
		\dim_set:Nn \l__karol_bhn_dim { 0.26em }   % halbe Steckerhöhe
	}
	% Senkrechte Mitte und Halshöhe des Steckers.  In der Oberfläche ist
	% der Hals schmaler als das Steckerende (Puzzlenase, keine Lasche):
	% Hals ~0.27, Ende ~0.42 der Blockhöhe.
	\dim_set:Nn \l__karol_bhm_dim
		{ 0.5\l__karol_bho_dim - 0.5\l__karol_bhu_dim }
	\dim_set:Nn \l__karol_bhk_dim { 0.62\l__karol_bhn_dim }
	% (em-Werte MÜSSEN vor dem Bild in Register: im tikzpicture ist em=0!)
	\mode_leave_vertical:
	\hbox:n
	{
		\begin{tikzpicture} [ baseline=0pt ]
			% Körper und Stecker in EINEM Pfad, damit die Umrandung außen
			% herumläuft statt quer durch den Stecker.
			\__karol_brand:nn {#1}
			{
				( \dim_use:N \l__karol_bhp_dim , \dim_use:N \l__karol_bho_dim )
				-- ( \dim_eval:n { \l__karol_bhp_dim + \l__karol_bhw_dim },
				     \dim_use:N \l__karol_bho_dim )
				-- ( \dim_eval:n { \l__karol_bhp_dim + \l__karol_bhw_dim },
				     -\dim_use:N \l__karol_bhu_dim )
				-- ( \dim_use:N \l__karol_bhp_dim ,
				     -\dim_use:N \l__karol_bhu_dim )
				\__karol_bbed_links:
				-- cycle
			}
			\node [ anchor=base, inner~sep=0pt ]
				at ( \dim_eval:n { \l__karol_bhp_dim + 0.5\l__karol_bhw_dim },
				     0pt )
				{ \box_use:N \l__karol_bhex_box };
		\end{tikzpicture}
	}
}

% Linke Seite des Bedingungsplättchens: Hals und Puzzlenase, von der
% unteren zur oberen Halskante.  Scharfe Ecken, weil an den Übergängen
% Gerade->Kurve die Segmente kürzer als zwei Eckradien sind — pgf machte
% daraus sonst Häkchen (dieselbe Falle wie bei der Kerbe).
\cs_new:Npn \__karol_bbed_links:
{
	\l__karol_bkerbe_tl
	-- ( \dim_use:N \l__karol_bhp_dim ,
	     \dim_eval:n { \l__karol_bhm_dim - \l__karol_bhk_dim } )
				% Puzzlenase: ausschwingen, runde Außenkante, einschwingen
				.. controls
					( \dim_eval:n { 0.72\l__karol_bhp_dim },
					  \dim_eval:n { \l__karol_bhm_dim - \l__karol_bhk_dim } )
					and
					( \dim_eval:n { 0.55\l__karol_bhp_dim },
					  \dim_eval:n { \l__karol_bhm_dim - \l__karol_bhn_dim } ) ..
					( \dim_eval:n { 0.22\l__karol_bhp_dim },
					  \dim_eval:n { \l__karol_bhm_dim - \l__karol_bhn_dim } )
				.. controls
					( \dim_eval:n { 0.02\l__karol_bhp_dim },
					  \dim_eval:n { \l__karol_bhm_dim - \l__karol_bhn_dim } )
					and
					( \dim_eval:n { 0.02\l__karol_bhp_dim },
					  \dim_eval:n { \l__karol_bhm_dim + \l__karol_bhn_dim } ) ..
					( \dim_eval:n { 0.22\l__karol_bhp_dim },
					  \dim_eval:n { \l__karol_bhm_dim + \l__karol_bhn_dim } )
				.. controls
					( \dim_eval:n { 0.55\l__karol_bhp_dim },
					  \dim_eval:n { \l__karol_bhm_dim + \l__karol_bhn_dim } )
					and
					( \dim_eval:n { 0.72\l__karol_bhp_dim },
					  \dim_eval:n { \l__karol_bhm_dim + \l__karol_bhk_dim } ) ..
					( \dim_use:N \l__karol_bhp_dim ,
					  \dim_eval:n { \l__karol_bhm_dim + \l__karol_bhk_dim } )
	\l__karol_bkante_tl
}

% --- Himmelsrichtungen: Ist / NichtIst mit Auswahlmenü ---------------------
% In Robot Karol Online zeigen diese acht Blöcke die Richtung nicht als
% Text, sondern als Auswahlmenü: eine helle Pille mit Auswahldreieck.
%
%     \bist{Norden}        \bnichtist{Süden}
%
% Beide liefern für sich ein vollständiges Bedingungsplättchen (wie im
% Original abgebildet).  Als Argument eines Kontrollblocks steuern sie
% dagegen nur dessen Beschriftung bei — dort baut \bbedingung das
% Plättchen, ein zweites wäre falsch:
%
%     \bwenndann{\bist{Norden}}{…}   ->   wenn <Ist [Norden v]> dann
%
% Unterschieden wird über \l__karol_bimbau_bool: innerhalb eines Blocks
% ist es wahr, auf oberster Ebene falsch.
\cs_new_protected:Npn \__karol_bist_text:nn #1#2
{
	% Die Pille gehört zum Bedingungsplättchen, nicht zum umgebenden
	% Block — sonst bekäme sie in \bwenndann dessen Grün.
	\tl_set:Nn \l__karol_bfarbe_tl { karolBlockBedingung }
	#1 ~ \__karol_bpille_menue:n {#2}
}
\cs_new_protected:Npn \__karol_bist:nn #1#2
{
	\bool_if:NTF \l__karol_bimbau_bool
		{ \__karol_bist_text:nn {#1} {#2} }
		{ \bbedingung { \__karol_bist_text:nn {#1} {#2} } }
}
\NewDocumentCommand { \bist }      { m } { \__karol_bist:nn { Ist }      {#1} }
\NewDocumentCommand { \bnichtist } { m } { \__karol_bist:nn { NichtIst } {#1} }

% --- Benutzerkommandos -----------------------------------------------------
% Jeder Nutzerblock läuft in einer eigenen Gruppe und bestimmt zu Beginn
% über \__karol_bstart:, ob er als Stapelblock oder inline gesetzt wird.
% \__karol_bwrap:n kapselt das für alle \b…-Kommandos.
\cs_new_protected:Npn \__karol_bwrap:n #1
	{ \group_begin: \__karol_farbschema: \__karol_bstart: #1 \group_end: }
% dito, aber mit erzwungenem Inline-Modus (\i…-Kommandos)
\cs_new_protected:Npn \__karol_iwrap:n #1
	{ \group_begin: \__karol_farbschema: \__karol_bstart_inline: #1
	  \group_end: }

% \bbefehl[Anzahl]{Name} — optionale Zahl erscheint als Pille
\NewDocumentCommand { \bbefehl } { O{} m }
{
	\__karol_bwrap:n
	{
		\__karol_bblock:nn { karolBlockBefehl }
			{ #2 \tl_if_empty:nF {#1} { ~ \__karol_bpille:n {#1} } }
	}
}

% \baufruf[Anzahl]{Name} — Aufruf einer eigenen Anweisung (rot).
% Der Name steht wie im Original in einer Auswahlpille mit Dreieck (dort
% wählt man daraus die aufzurufende Anweisung); die Definition
% \banweisung zeigt ihn dagegen in einer Pille OHNE Dreieck.
\NewDocumentCommand { \baufruf } { O{} m }
{
	\__karol_bwrap:n
	{
		\__karol_bblock:nn { karolBlockAnweisung }
			{ \__karol_bpille_menue:n {#2}
			  \tl_if_empty:nF {#1} { ~ \__karol_bpille:n {#1} } }
	}
}

% \banweisung{Name}{Blöcke} — Definition einer eigenen Anweisung (C-Block)
\NewDocumentCommand { \banweisung } { m m }
{
	\__karol_bwrap:n
	{
		\__karol_bcblock:nnn { karolBlockAnweisung }
			{ Anweisung ~ \__karol_bpille:n {#1} } { #2 }
	}
}

% \bkommentar{Text} — Kommentarblock (khaki) mit //-Präfix
\NewDocumentCommand { \bkommentar } { m }
{
	\__karol_bwrap:n
	{
		\__karol_bblock:nn { karolBlockKommentar }
			{ { \normalfont \__karol_textfont: \color{black!70} // } ~
			  \__karol_bpille:n {#1} }
	}
}

% \bhauptprogramm — Hut-Block (ohne obere Kerbe), startet den Stapel
\cs_new_protected:Npn \__karol_bhut:
{
	\__karol_bmess:n { Hauptprogramm }
	\__karol_bemit:nN
	{
		\begin{tikzpicture}
			\useasboundingbox (0pt, 0pt) rectangle
				( \dim_use:N \l__karol_bw_dim ,
				  \__karol_bbbox:N \l__karol_bh_dim );
			\__karol_brand:nn { karolBlockHut }
			{
				(0pt, 0pt)
				-- ( \dim_use:N \l__karol_bw_dim , 0pt )
				\__karol_pfad_unten:n \l__karol_bh_dim
			}
			\__karol_btxt_setzen:n { 0pt }
		\end{tikzpicture}
	}
	\l__karol_bh_dim
}
\NewDocumentCommand { \bhauptprogramm } { }
	{ \__karol_bwrap:n { \__karol_bhut: } }

\NewDocumentCommand { \bwiederholemal } { m m }
{
	\__karol_bwrap:n
	{
		\__karol_bcblock:nnn { karolBlockSchleife }
			{ wiederhole ~ \__karol_bpille:n {#1} ~ mal } { #2 }
	}
}
\NewDocumentCommand { \bwiederholesolange } { O{} m m }
{
	\__karol_bwrap:n
	{
		\__karol_bcblock:nnn { karolBlockSchleife }
			{ wiederhole ~ solange ~ \bbedingung [#1] {#2} } { #3 }
	}
}
\NewDocumentCommand { \bwiederholeimmer } { m }
{
	\__karol_bwrap:n
		{ \__karol_bcblock:nnn { karolBlockSchleife }
			{ wiederhole ~ immer } { #1 } }
}

\NewDocumentCommand { \bwenndann } { O{} m m }
{
	\__karol_bwrap:n
	{
		\__karol_bcblock:nnn { karolBlockWenn }
			{ wenn ~ \bbedingung [#1] {#2} ~ dann } { #3 }
	}
}
\NewDocumentCommand { \bwenndannsonst } { O{} m m m }
{
	\__karol_bwrap:n
	{
		\__karol_beblock:nnnn { karolBlockWenn }
			{ wenn ~ \bbedingung [#1] {#2} ~ dann } { #3 } { #4 }
	}
}

% Leerer „Geisterblock" zum handschriftlichen Ausfüllen
\cs_new_protected:Npn \__karol_bluecke:n #1
{
	\__karol_bdims:
	\dim_set:Nn \l__karol_bw_dim { #1 }
	\dim_set:Nn \l__karol_bh_dim
		{ \bool_if:NTF \l__karol_binline_bool { 1.20em } { 1.70em } }
	% Grundlinie wie bei einem beschrifteten Block: die Lücke sitzt mit
	% ihrer gedachten Textgrundlinie auf der Grundlinie des Fließtextes.
	\hbox_set:Nn \l__karol_btxt_box
		{ \__karol_blockfont: \__karol_bphantom: }
	\__karol_bbase_setzen:
	% Strichmuster in em rechnen, damit es mit der Blockgröße mitwächst —
	% ein festes 3pt-Muster ist für die knapp 2pt langen Kerbensegmente des
	% Inline-Blocks zu grob und zerreißt die Kontur.  em MUSS vorher ins
	% Register: im tikzpicture ist es 0pt.
	\dim_set:Nn \l__karol_bstr_dim { 0.17em }
	\dim_set:Nn \l__karol_bluk_dim { 0.13em }
	% Füllung: im Druck weiß statt hellgrau — der gestrichelte Rand sagt
	% schon alles, die Fläche wird ohnehin von Hand beschrieben.
	% (Der Test gehört NICHT in den TikZ-Schlüsselwert, siehe xcolor.)
	\bool_if:NTF \l__karol_druck_bool
		{ \tl_set:Nn \l__karol_bluf_tl { white } }
		{ \tl_set:Nn \l__karol_bluf_tl { black!6 } }
	\__karol_bemit:nN
	{
		\begin{tikzpicture}
			\useasboundingbox (0pt, 0pt) rectangle
				( \dim_use:N \l__karol_bw_dim ,
				  \__karol_bbbox:N \l__karol_bh_dim );
			\draw [ draw=black!55, line~width=.7pt,
			        dash~pattern=on~\l__karol_bstr_dim~off~\l__karol_bluk_dim,
			        line~join=round, line~cap=round,
			        fill=\l__karol_bluf_tl, rounded~corners=1.6pt ]
				\__karol_pfad_oben: \__karol_pfad_unten:n \l__karol_bh_dim ;
		\end{tikzpicture}
	}
	\l__karol_bh_dim
}
\NewDocumentCommand { \bluecke } { O{7em} }
	{ \__karol_bwrap:n { \__karol_bluecke:n {#1} } }

% ---------------------------------------------------------------------------
%  Inline-Blöcke für den Fließtext
% ---------------------------------------------------------------------------
%  Die \i…-Kommandos setzen genau einen Block in die laufende Zeile:
%  in der Schriftgröße des umgebenden Textes, mit dessen Grundlinie
%  bündig und mit knapperen Verzierungen als im Stapel.  Blöcke, die im
%  Stapel mehrzeilig wären (Schleifen, Verzweigungen, eigene Anweisungen),
%  erscheinen inline nur mit ihrem Kopf — \iwenndann{IstZiegel} ergibt
%  also den Block „wenn ⟨IstZiegel⟩ dann".  Für echte Rümpfe bleibt die
%  Umgebung karolbloecke zuständig.
%
%  Auch die \b…-Kommandos erkennen den Fließtext selbst und setzen dort
%  automatisch inline; die \i…-Fassungen ersparen nur die leeren
%  Rumpf-Argumente und erzwingen den Modus auch im Stapel.
% ---------------------------------------------------------------------------
\NewDocumentCommand { \ibefehl } { O{} m }
{
	\__karol_iwrap:n
	{
		\__karol_bblock:nn { karolBlockBefehl }
			{ #2 \tl_if_empty:nF {#1} { ~ \__karol_bpille:n {#1} } }
	}
}
\NewDocumentCommand { \iaufruf } { O{} m }
{
	\__karol_iwrap:n
	{
		\__karol_bblock:nn { karolBlockAnweisung }
			{ \__karol_bpille_menue:n {#2}
			  \tl_if_empty:nF {#1} { ~ \__karol_bpille:n {#1} } }
	}
}
\NewDocumentCommand { \ianweisung } { m }
{
	\__karol_iwrap:n
	{
		\__karol_bblock:nn { karolBlockAnweisung }
			{ Anweisung ~ \__karol_bpille:n {#1} }
	}
}
\NewDocumentCommand { \ikommentar } { m }
{
	\__karol_iwrap:n
	{
		\__karol_bblock:nn { karolBlockKommentar }
			{ { \normalfont \__karol_textfont: \color{black!70} // } ~
			  \__karol_bpille:n {#1} }
	}
}
\NewDocumentCommand { \ihauptprogramm } { }
	{ \__karol_iwrap:n { \__karol_bhut: } }
\NewDocumentCommand { \iwiederholemal } { m }
{
	\__karol_iwrap:n
	{
		\__karol_bblock:nn { karolBlockSchleife }
			{ wiederhole ~ \__karol_bpille:n {#1} ~ mal }
	}
}
\NewDocumentCommand { \iwiederholesolange } { O{} m }
{
	\__karol_iwrap:n
	{
		\__karol_bblock:nn { karolBlockSchleife }
			{ wiederhole ~ solange ~ \bbedingung [#1] {#2} }
	}
}
\NewDocumentCommand { \iwiederholeimmer } { }
{
	\__karol_iwrap:n
		{ \__karol_bblock:nn { karolBlockSchleife } { wiederhole ~ immer } }
}
\NewDocumentCommand { \iwenndann } { O{} m }
{
	\__karol_iwrap:n
	{
		\__karol_bblock:nn { karolBlockWenn }
			{ wenn ~ \bbedingung [#1] {#2} ~ dann }
	}
}
% \isonst — der „sonst"-Balken einer Verzweigung als eigener Inline-Block
\NewDocumentCommand { \isonst } { }
	{ \__karol_iwrap:n { \__karol_bblock:nn { karolBlockWenn } { sonst } } }
% \ibedingung[Anzahl]{…} — Bedingungsplättchen im Fließtext (erzwungen
% inline); die Zahl erscheint als Pille: \ibedingung[2]{IstZiegel}
\NewDocumentCommand { \ibedingung } { O{} m }
	{ \__karol_iwrap:n { \bbedingung [#1] {#2} } }
% Himmelsrichtungen inline
\NewDocumentCommand { \iist } { m }
	{ \__karol_iwrap:n { \bbedingung { \__karol_bist_text:nn { Ist } {#1} } } }
\NewDocumentCommand { \inichtist } { m }
{
	\__karol_iwrap:n
		{ \bbedingung { \__karol_bist_text:nn { NichtIst } {#1} } }
}
\NewDocumentCommand { \iluecke } { O{4em} }
	{ \__karol_iwrap:n { \__karol_bluecke:n {#1} } }

\ExplSyntaxOff

% ===========================================================================
%  6. Verweise auf Aufgaben in Robot Karol Online
% ===========================================================================
%  Eine Aufgabe in Robot Karol Online steckt vollständig in der URL:
%  auf https://karol.arrrg.de/ folgt eine Raute und der Aufgabencode.
%
%      \karolonline{2t7t}   setzt   karol.arrrg.de/#2t7t
%                           und verlinkt auf https://karol.arrrg.de/#2t7t
%
%  Gesetzt wird nur, was man tatsächlich eintippen muss (ohne "https://"),
%  in der Programmschrift des Pakets.  Der Code wird unverändert
%  übernommen — auf Groß- und Kleinschreibung kommt es bei den
%  Aufgabencodes nicht an.
%
%  Der Verweis braucht hyperref.  Ob das Paket es lädt, entscheidet die
%  Paketoption:
%
%      \usepackage{robotkarol}                   lädt hyperref (Vorgabe)
%      \usepackage[hyperref=false]{robotkarol}   lädt es nicht
%
%  Ohne hyperref setzt \karolonline denselben Text, nur ohne Verweis.
%  Geladen wird im Haken "begindocument/before", also am Ende der
%  Präambel: hyperref will als letztes Paket geladen werden, und ein
%  Dokument, das es selbst mit eigenen Optionen lädt, käme sonst zu einem
%  Optionskonflikt.  Geladen wird ohne Optionen: wie Verweise aussehen,
%  ist Sache des Dokuments.  Aus demselben Grund fragt \karolonline nicht die
%  Paketoption ab, sondern ob es \href zum Zeitpunkt der Benutzung
%  wirklich gibt — dann verlinkt es auch in einem Dokument, das hyperref
%  trotz hyperref=false selbst geladen hat.
% ---------------------------------------------------------------------------
\ExplSyntaxOn

% Ohne Optionen geladen: wie Verweise aussehen (Rahmen, Farben), stellt
% das Dokument selbst über hyperref ein — das Paket redet da nicht hinein.
\bool_if:NT \g__karol_href_bool
	{ \AddToHook { begindocument/before } { \RequirePackage { hyperref } } }

% Beides als STRING, nicht als tl: unter \ExplSyntaxOn ist ":" ein
% Buchstabe (Catcode 11).  hyperref zerlegt die URL aber mit einem
% delimitierten Argument (\href@split #1:#2:#3), und das trifft nur
% Doppelpunkte mit Catcode 12 — sonst erscheint der Text, aber es
% entsteht kein Verweis, und zwar ohne jede Fehlermeldung.
\str_const:Nn \c__karol_obasis_str   { https://karol.arrrg.de/ }
\str_const:Nn \c__karol_oanzeige_str { karol.arrrg.de/ }
\str_new:N   \l__karol_ocode_str

% Der sichtbare Text: das, was man eintippen muss.
\cs_new_protected:Npn \__karol_online_text:
{
	{ \__karol_monofont: \str_use:N \c__karol_oanzeige_str \c_hash_str
	  \str_use:N \l__karol_ocode_str }
}
% #1 = fertige URL (x-expandiert, damit die Raute wirklich ankommt)
\cs_new_protected:Npn \__karol_online_href:n #1
	{ \href {#1} { \__karol_online_text: } }

\NewDocumentCommand { \karolonline } { m }
{
	\group_begin:
	% \str_set:Nn statt \tl_set:Nn — der Code muss Catcode 12 haben,
	% damit hyperref die URL zerlegen kann (siehe oben).
	\str_set:Nn \l__karol_ocode_str {#1}
	\cs_if_exist:NTF \href
	{
		\exp_args:Nx \__karol_online_href:n
			{ \c__karol_obasis_str \c_hash_str \l__karol_ocode_str }
	}
	{ \__karol_online_text: }
	\group_end:
}

\ExplSyntaxOff

\endinput
