đ§± Architektur â Netzwerk-Labor
âŹïž Netzwerk-Labor · Spezifikation: Konzept â Netzwerk-Labor
Diese Notiz ist der Vertrag zwischen den Bausteinen. Wer ein Modul baut, hÀlt sich an die Namen, Datenformen und Aufrufe hier. Wer etwas Àndern muss, Àndert es hier zuerst.
1 · GrundsÀtze
- Eigenes Desktop-Programm (Tauri 2). Die Spielhalle bleibt unberĂŒhrt; nur
lernmotor.jswird zur Bauzeit aus../FISI-Spielhalle/src/eingebunden (Konzept § 8.3). - Schlichtes JavaScript, keine ES-Module, kein npm, kein Bundler. Jede Datei ist ein klassisches Skript.
bauen.pyhÀngt sie in fester Reihenfolge zusammen. - NamensrÀume statt Globals-Wildwuchs. Jede Schicht hat genau ein globales Objekt, das ihre erste Datei anlegt, alle weiteren erweitern es:
| Schicht | Objekt | Erste Datei | DOM? | Zeit/Zufall? |
|---|---|---|---|---|
| Kern | IP, Bus, Zufall, store, heute, plusTage, tageZwischen, tief, esc | kern/basis.js, kern/netz.js | nein | nur jetzt() in basis |
| Modell | Modell | modell/geraete.js | nein | nein |
| Simulation | Sim | sim/engine.js | nie | nie (virtuelle Zeit, Seed) |
| Konsole | CLI | cli/parser.js | nie | nie |
| Spiel | Spiel | spiel/zustand.js | nein | nur ĂŒber jetzt() |
| Daten | DATEN | daten/lehrtexte.js | nein | nein |
| Plattform | Plattform | plattform/plattform.js | ja | ja |
| OberflÀche | UI | ui/dom.js | ja | ja |
| Lernmotor | L | Spielhalle lernmotor.js | nein | Datum |
sim/,cli/,modell/,kern/,spiel/,daten/laufen headless in Node (tests/run.js). Deshalb: keindocument, keinwindow, keinDate.now()/Math.random()insim/undcli/. Spiel nutztjetzt()aus basis (in Tests ersetzbar).- Deutsche Bezeichner wie in der Spielhalle. Anrede im Spiel âduâ.
2 · Build und Ordner
Netzwerk-Labor/
bauen.py â web/index.html (Einzeldatei + web/schriften/), --paket â Browser-ZIP (Nebenprodukt)
src/seite.html HĂŒlle mit /*STIL*/, /*SKRIPTE*/, <!--SCHRIFTEN-->
src/stil/*.css Reihenfolge in bauen.py (STILE)
src/**/*.js Reihenfolge in bauen.py (MODULE)
shell/src-tauri/ Tauri-Projekt (frontendDist = ../../web)
tests/ harness.js, run.js (Node), tests.html (Browser), *.test.js
tools/ messen.ps1, linux-bauen.sh, cdp.py (Test im echten Programm), klassen.py (Klassen â CSS), rauch.py (Rauchtest der OberflĂ€che), kiosk.py (VorfĂŒhr-Fassung)
web/ Build-Ausgabe
Neue Datei? In bauen.py in MODULE (bzw. STILE) an der richtigen Stelle eintragen, und â falls headless-fĂ€hig â in tests/run.js in HEADLESS. Eine Datei, die nicht in MODULE steht, existiert fĂŒr das Programm nicht.
Tests: sh tools/test.sh [filter] (Node 24 portabel; frĂŒher WSL). Testdateien nutzen pruefe(name, fn) und erwarte-Hilfen aus tests/harness.js. Die Tests laden ui/ nicht â dafĂŒr gibt es sh tools/test.sh --rauch: Node-Tests, tools/klassen.py (muss 0 melden) und tools/rauch.py (jede Ansicht in 1366/960/720 px in Edge headless: Hauptaktion sichtbar und anklickbar, kein waagerechter Ăberlauf, keine JS-Fehler; --exe prĂŒft das echte Programm).
3 · Kern (kern/basis.js, kern/netz.js)
store.get(k, d),store.set(k, v)â synchron, Cache mit Durchschreiben (Konzept § 9.3). SchlĂŒssel:"lern"(Lernmotor),"labor"(Spielstand),"einst"(Einstellungen).heute(),plusTage(tag, n),tageZwischen(a, b)â wie Spielhalle (der Lernmotor braucht sie).jetzt()â Millisekunden; in Tests ĂŒberjetzt.setzen(ms)steuerbar.Bus.an(name, fn),Bus.aus(name, fn),Bus.senden(name, daten).Zufall(seed)â{zahl(n), wahl(arr), mischen(arr), kommazahl()}(deterministisch, mulberry32).tief(x)tiefe Kopie (JSON-sicher),esc(s)HTML-Escape.IP:zuZahl(s),zuText(n),gueltig(s),maskeGueltig(s),praefix(maske),maske(praefix),netz(ip, maske),broadcast(ip, maske),gleichesNetz(a, b, maske),imNetz(ip, netz, maske),wildcardPasst(ip, basis, wc),privat(ip),apipa(ip),cidr(ip, maske)â"192.168.1.0/24",macCisco(mac)â"0060.2f3a.1b01",macWindows(mac)â"00-60-2F-3A-1B-01".
Nachtrag 31.10.2026 (KRN-01, aus dem Code gemessen). VollstÀndige Kern-OberflÀche:
store.get(k, d)/set(k, v)arbeiten mit tiefen Kopien;markieren()/standMelden()setzen âschmutzig" und starten die Entprellung;sofort()schreibt sofort und liefert ein immer erfĂŒlltesPromise<boolean>(Fehler ĂŒberstand()/Bus);stand()â{art:"schreibt"|"gesichert"|"fehler", fehler, zeit};alles()â tiefe Kopie des ganzen Speichers;initialisieren(daten, schreiber, meldung);schreiberSetzen(fn);meldung;entprellungMs. Genau eine Entprellung:SPEICHER.entprellung = 1500ms (src/kern/basis.js:71-132).Bus.an(name, fn)(RĂŒckgabe = Abmeldefunktion),Bus.aus(name, fn),Bus.senden(name, daten); Hörer-Ausnahmen werden gefangen und geloggt (src/kern/basis.js:28-39).jetzt()â in Tests ĂŒberjetzt.setzen(ms)/weiter(ms)/frei()steuerbar (src/kern/basis.js:8-15);heute(d?),plusTage(tag, n),tageZwischen(a, b),datumDe(tag)(:18-21).Zufall(seed)â{zahl(n), wahl(arr), mischen(arr), kommazahl(), zwischen(min, max), seed}(mulberry32, deterministisch;src/kern/basis.js:42-56).tief(x),esc(s)(:24-25); Anzeige-Helfereur(x),zahlDe(x),klemme(x, lo, hi)(:150-152).IP: wie oben; zusĂ€tzlichausCidr(s)â{netz, maske, praefix},istNetzadresse(ip,m),istBroadcast(ip,m),hostAdresse(ip,m),wildcard(m),plus(ip,k),vergleich(a,b),loopback(ip),maskeZahl(p),macAus(n),MAC_BROADCAST(src/kern/netz.js:47-69).- FrĂŒhstart:
window.__LABOR_SPEICHER__(Tauri, von der HĂŒlle vor allen Seitenskripten gesetzt) hat Vorrang vorlocalStorage["netzwerk-labor"]; ein Lesefehler startet leer mit Meldung (src/kern/basis.js:134-147). Ein kaputter oder zu neuer Browser-Stand wird vor dem ersten Schreiben unternetzwerk-labor-beschaedigtaufbewahrt; die Meldung nennt die Sicherung (SPEICHER_SCHEMA_MAX = 3, Spielstand v:3) (KRN-03).
4 · Modell (Konfiguration ist Daten)
Ein Netz ist reines JSON:
{ v:1,
geraete: { [id]: Geraet },
kabel: [ { id:"k1", a:{geraet:"pc1", port:"eth0"}, b:{geraet:"sw1", port:"Fa0/1"} } ],
zustand: { _uhr:0, [id]: Laufzeit } // Laufzeit der Simulation (ARP, MAC-Tabellen âŠ), siehe § 5.4
}
Geraet
{ id:"r1", typ:"pc"|"server"|"switch"|"router"|"firewall"|"internet"|"nas",
skin:null|"laptop"|"drucker"|"kasse"|"tablet", // nur Optik bei pc
name:"R1", x:400, y:200, an:true,
hw:{ macs:{ "Gi0/0":"00:1a:2b:3c:4d:01", ⊠} }, // je physischem Port, fest
running: Konfig, startup: Konfig|null, // null = Werkszustand (kein startup-config)
flash: { vlans:{ "1":{name:"default"}, "10":{name:"Verwaltung"} } } // nur switch: vlan.dat
}
Ports je Typ (Modell.PORTS): pc/server ["eth0"] · nas ["eth0","eth1"] · switch Fa0/1âŠFa0/24, Gi0/1, Gi0/2 · router Gi0/0, Gi0/1, Gi0/2 · firewall Gi0/0âŠGi0/3 · internet wan0âŠwan3. Subinterfaces (Gi0/0.10) sind nur Konfiguration, keine Ports.
Konfig je Typ (Werkszustand = Modell.werkszustand(typ)):
// pc, server, nas
{ hostname:"PC1",
if:{ eth0:{ an:true, dhcp:false, ip:"", maske:"", gw:"", dns:"" } },
dienste:{ http:{an:true}, https:{an:false}, dns:{an:false, eintraege:[{name:"intranet.salon.local", ip:"192.168.1.5"}]},
dhcp:{an:false, pools:[{name, netz, maske, gw, dns, start, anzahl}]}, datei:{an:false}, ssh:{an:false}, druck:{an:false} } }
// Dienst â Port: http tcp/80, https tcp/443, dns udp/53, dhcp udp/67, datei tcp/445, ssh tcp/22, druck tcp/9100 (Modell.DIENSTPORTS)
// switch (VLANs stehen in geraet.flash.vlans, NICHT in der Konfig â wie vlan.dat)
{ hostname:"Switch", enableSecret:null, banner:"",
ports:{ "Fa0/1":{ modus:"access"|"trunk", accessVlan:1, trunkErlaubt:"all"|[1,10,20], nativeVlan:1, shutdown:false, beschreibung:"",
portSecurity:null|{max:1, verstoss:"shutdown", macs:[]} }, ⊠},
svi:{ "1":{ ip:"", maske:"", shutdown:true } },
defaultGateway:"" }
// router
{ hostname:"Router", enableSecret:null, banner:"",
if:{ "Gi0/0":{ ip:"", maske:"", shutdown:true, beschreibung:"", nat:null|"inside"|"outside", aclIn:null, aclOut:null, helper:[] },
"Gi0/0.10":{ vlan:10, nativ:false, ip:"", maske:"", shutdown:false, beschreibung:"", nat:null, aclIn:null, aclOut:null, helper:[] } },
routen:[ { netz:"10.0.2.0", maske:"255.255.255.0", nh:"10.0.1.2"|null, aus:"Gi0/1"|null, ad:1 } ],
acls:{ "10":{ typ:"standard", benannt:false, regeln:[Regel] }, "GAST":{ typ:"erweitert", benannt:true, regeln:[Regel] } },
nat:{ statisch:[{ innen:"192.168.1.10", aussen:"203.0.113.10", proto:null|"tcp"|"udp", innenPort:null, aussenPort:null }],
dynamisch:[{ acl:"1", aus:"Gi0/1", overload:true }] },
dhcp:{ ausgeschlossen:[{von:"192.168.10.1", bis:"192.168.10.9"}], pools:[{ name:"LAN", netz:"192.168.10.0", maske:"255.255.255.0", gw:"192.168.10.1", dns:"" }] } }
// Regel (ACL)
{ aktion:"permit"|"deny", proto:"ip"|"tcp"|"udp"|"icmp",
quelle:{ip:"192.168.1.0", wc:"0.0.0.255"}, ziel:{ip:"0.0.0.0", wc:"255.255.255.255"}, // any = 0.0.0.0/255.255.255.255, host = wc 0.0.0.0
zielPort:null|{op:"eq"|"neq"|"gt"|"lt"|"range", ports:[80]}, quellPort:null|{âŠ}, icmpTyp:null|"echo"|"echo-reply" }
// Standard-ACL: nur quelle, proto "ip". Implizites deny any am Ende.
// firewall (herstellerneutral)
{ hostname:"FW1",
if:{ "Gi0/0":{ ip:"", maske:"", zone:"innen"|"aussen"|"dmz"|null, shutdown:false } },
routen:[âŠwie routerâŠ],
regeln:[ { id:"r1", von:"innen", nach:"aussen", proto:"ip"|"tcp"|"udp"|"icmp", quelle:"any"|"192.168.1.0/24", ziel:"any"|"cidr", port:null|443, aktion:"erlauben"|"verwerfen", aktiv:true, text:"" } ],
nat:{ quellNat:[{von:"innen", nach:"aussen"}], weiterleitung:[{ proto:"tcp", aussenPort:443, ziel:"172.16.0.10", zielPort:443 }] } }
// Erste passende Regel gewinnt, am Ende implizit verwerfen, zustandsbehaftet (RĂŒckverkehr erlaubt).
// internet (Kulisse, nicht konfigurierbar im Spiel)
{ hostname:"Internet",
if:{ wan0:{ ip:"203.0.113.1", maske:"255.255.255.252" }, ⊠},
routen:[ {netz:"203.0.113.16", maske:"255.255.255.240", nh:"203.0.113.2"} ],
server:[ { name:"www.beispiel.de", ip:"198.51.100.10", dienste:["http","https"] }, { name:"dns.beispiel.de", ip:"198.51.100.53", dienste:["dns"] } ] }
// Verwirft Pakete mit privater Quelladresse (Grund NAT_MISSING).
Modell-API (modell/*.js): Modell.neu(), Modell.geraet(netz, typ, {id?, name?, x, y, skin?}) (legt an, vergibt MACs), Modell.entfernen(netz, id), Modell.ports(geraet), Modell.verbinden(netz, a, b) (a/b = {geraet, port?}; ohne Port â nĂ€chster freier passender) â Kabel oder {fehler}, Modell.trennen(netz, kabelId), Modell.kabelAn(netz, id, port) â {kabel, gegen:{geraet,port}}|null, Modell.freierPort(netz, id), Modell.linkOben(netz, kabel) â {oben, grund}, Modell.konfig(geraet) (= running), Modell.setzen(netz, id, pfad, wert) (Pfad wie "if.eth0.gw"), Modell.speichern(geraet) (running â startup), Modell.neustart(netz, id) (running := tief(startup) oder Werkszustand, Laufzeit leeren), Modell.kopie(netz), Modell.pruefen(netz) â Warnungen [{geraet, feld, text, code}] (Live-PrĂŒfung: â10.0.0.5/24 liegt nicht im Netz von Gateway 10.0.1.1â), Modell.gleich(a, b).
RĂŒckgĂ€ngig (modell/verlauf.js): Modell.verlauf(netz) â {aendern(beschreibung, fn), zurueck(), vor(), kannZurueck, kannVor, liste}. Schnappschuss-basiert (Netz ist klein), höchstens 100 Schritte. Jede UI- und KonsolenĂ€nderung geht ĂŒber aendern. Danach Bus.senden("netz-geaendert", {netz, beschreibung}).
Nachtrag 31.10.2026 (KRN-01, aus dem Code gemessen).
- GerÀteform:
os:"windows"|"linux"nur beipc/server/nas, gespeichert nur als Abweichung vom Standard (OS_STANDARD: pc windows, server/nas linux;Modell.osVon,src/modell/geraete.js:25-33); Switch:snooping:{an, vertraut:[port]}(:56-58); Router:dhcp:{an, ausgeschlossen:[âŠ], pools:[âŠ]}â fehlendesangilt als an (:62-64). - Modell-API â vollstĂ€ndige exportierte OberflĂ€che (
src/modell/geraete.js:295-298):TYPEN,NAMEN,PORTS,DIENSTPORTS,IOS,HOST,OS_STANDARD,osVon,werkszustand,subIf,switchPort,routerIf,hostIf,neu,geraet,entfernen,ports,kabelAn,freierPort,verbinden,trennen,portAn,linkOben,portStatus,pfad,lesen,setzen,loeschen,geraetSetzen,vlan,laufzeit,macsVergessen,speichern,neustart,startupLoeschen,kopie,gleich,ungespeichert,adressen,pruefenâ[{geraet, feld, code, text}],konfig(= running). RĂŒckgĂ€ngig:Modell.verlauf(netz)â{aendern, zurueck, vor, kannZurueck, kannVor, liste, leeren}(src/modell/verlauf.js:5-42). - Werkszustand (Ist-Stand):
pchat leeresdienste(src/modell/geraete.js:43-53);internetstartet ohneif-Adressen und ohne Routen (:70-76). Die frĂŒheren Abweichungen âpc ohne dienste" und âinternet ohne if/routen" sind damit dokumentierter Ist-Stand, kein Widerspruch mehr. - Bekannte Abweichungen (nicht Vertrag; gehören zu KRN-06):
loeschenprĂŒft das GerĂ€t nicht (:210-215);geraetSetzenschreibt andere Felder direkt (:228-236);adressenliefert auch abgeschaltete SVI (:262);gleichvergleicht perJSON.stringify(:254).
5 · Simulation (Sim)
5.1 Aufrufe (verÀndern netz.zustand, nie die Konfig)
Sim.ping(netz, vonId, ziel, {anzahl:4, ttl:null, quelle:null}) // ziel = IP oder GerÀtename (DNS)
â { ok, antworten:[{ok, grund|null, rtt, ttl, von}], trace, text }
Sim.traceroute(netz, vonId, zielIp, {maxHops:30}) â { hops:[{nr, ip|null, grund|null}], ok, trace }
Sim.tcp(netz, vonId, ziel, port) â { ok, grund, trace } // Handshake SYN, SYN/ACK, ACK (+FIN)
Sim.http(netz, vonId, url) â { ok, status, grund, trace } // DNS + TCP 80/443 + GET
Sim.dns(netz, vonId, name) â { ok, ip, grund, trace }
Sim.dhcp(netz, vonId, port="eth0") â { ok, lease|null, grund, trace } // DORA; bei Fehlschlag APIPA
Sim.adresse(netz, id, port) â { ip, maske, gw, dns, quelle:"statisch"|"dhcp"|"apipa"|"keine" }
Sim.pruefeZiel(netz, ziel) â { ok, grund, trace, text } // Ziel siehe § 7.1; nutzt eine Kopie
Sim.GRUENDE[code] â { titel, skill, schicht } // Lehrtexte in DATEN.lehrtexte[code]
Hosts mit dhcp:true und ohne gĂŒltige Lease holen sich zuerst per DHCP eine Adresse (sichtbar in derselben Trace). Scheitert das: APIPA 169.254.x.x und Grund DHCP_NO_OFFER.
5.2 Trace (reine Daten, die UI spielt nur ab)
Trace = { start, ende, ereignisse:[Ereignis], abbruch:null|"STORM", zusammenfassung:"âŠ" }
Ereignis = {
n:1, t:0.8, // laufende Nr., virtuelle Zeit in ms
art:"senden"|"empfangen"|"weiterleiten"|"fluten"|"verwerfen"|"antworten"|"lernen"|"info",
geraet:"sw1", port:"Fa0/1"|null,
nach:{geraet, port}|null, // bei "senden": Gegenstelle des Kabels
frame: Frame, // Kopie, unverÀnderlich
proto:"ARP"|"ICMP"|"TCP"|"UDP"|"DHCP"|"DNS"|"HTTP",
grund:null|"ARP_NO_REPLY"|âŠ, // bei verwerfen/info
warnung:true, // optional: nur bei Hinweisen (z. B. CDP NATIVE_MISMATCH) â keine Ursache; _erster/stelle/erklaere werten solche Ereignisse NICHT als entscheidende Stelle
harmlos:true, // optional: normales/gewolltes Verwerfen ohne Grund (z. B. IOS verwirft Paket wĂ€hrend ARP, NIC verwirft fremde MAC) â kein Fehler; _erster/stelle/erklaere werten es nicht als Ursache
text:"SW1 kennt MAC 00:1a⊠noch nicht und flutet in VLAN 10" // kurzer Klartext
}
Frame = {
eth:{ src, dst, typ:"ARP"|"IPv4", vlan:null|10 }, // vlan = 802.1Q-Tag (nur auf Trunk sichtbar)
arp:null|{ op:"request"|"reply", senderMac, senderIp, targetMac, targetIp },
ip:null|{ src, dst, ttl, proto:"ICMP"|"TCP"|"UDP", id },
icmp:null|{ typ:"echo-request"|"echo-reply"|"unreachable"|"time-exceeded", code:null|"net"|"host"|"port"|"admin", seq },
udp:null|{ src, dst }, tcp:null|{ src, dst, flags:["SYN","ACK"], seq, ack },
app:null|{ proto:"DHCP"|"DNS"|"HTTP", info:"DHCP Discover", felder:{âŠ} }
}
Ereignis-Budget je Lauf 5000; gerissen â abbruch:"STORM", Grund STORM. Warnungen (warnung:true) sind Hinweise und dĂŒrfen die Diagnose nicht verfĂ€lschen: Sim._erster, Sim.stelle und Sim.erklaere ĂŒberspringen sie (SIM-02, 31.10.2026); die Warnung bleibt vollstĂ€ndig in der Trace. Virtuelle Zeit: 1 ms je Kabel, Timeout Ping 2000 ms (IOS) bzw. 4000 ms (Windows). Verwerfen ohne Fehlergrund trĂ€gt harmlos:true (normal/gewollt). Jedes Verwerfen hat damit grund oder harmlos (SIM-05/06); die 21 bisher unbegrĂŒndeten Stellen sind so klassifiziert.
5.3 GrĂŒnde (Codes)
LINK_DOWN, PORT_SHUTDOWN, NO_IP, ARP_NO_REPLY, DROP_VLAN, TRUNK_NOT_ALLOWED, NATIVE_MISMATCH, NO_GATEWAY, GW_WRONG_SUBNET, GW_UNREACHABLE, WRONG_MASK, NO_ROUTE, NO_RETURN_ROUTE, TTL_EXPIRED, ACL_DENY, FW_DENY, NAT_MISSING, DUP_IP, DHCP_NO_OFFER, DHCP_POOL_EMPTY, DNS_FAIL, DNS_NO_SERVER, PORT_CLOSED, SERVICE_OFF, STORM, PORTSEC_VIOLATION, DEVICE_OFF, HOST_UNREACHABLE, TIMEOUT
Sim.GRUENDE[code] = {titel, skill:"lab.*", schicht:1..7}. Lehrtexte in drei Tiefen stehen in DATEN.lehrtexte[code] = {E, AP1, AP2, quelle} (E erklĂ€rt, AP1 knapp, AP2 nur, was das GerĂ€t melden wĂŒrde).
5.4 Laufzeit (netz.zustand[id])
{ arp:{ [ip]:{mac, bis} }, mac:{ [vlan]:{ [mac]:{port, bis} } }, leases:{ [ip]:{mac, bis} }, dhcp:{ [port]:{ip, maske, gw, dns, server, bis} }, nat:[âŠ], fw:[âŠ] } plus zustand._uhr (virtuelle Zeit, lĂ€uft ĂŒber LĂ€ufe weiter). MAC-Alterung 300 s, ARP 14400 s (Router) bzw. 120 s (Host, vereinfacht). Link down leert die MAC-EintrĂ€ge dieses Ports; IP-Ănderung leert den ARP-Cache des GerĂ€ts (Modell.setzen erledigt das).
6 · Konsole (CLI)
CLI.sitzung(netz, geraetId, {verlauf?}) â Sitzung // IOS fĂŒr switch/router/firewall(GrundzĂŒge), Windows-Terminal fĂŒr pc/server/nas
CLI.eingabe(sitzung, zeile) â { ausgabe:"âŠ", prompt:"R1(config-if)#", geaendert:bool, befehl:"interface gigabitEthernet0/0" }
CLI.tab(sitzung, zeile) â { zeile, vorschlaege:[] }
CLI.prompt(sitzung) â "R1#"
CLI.runningConfig(geraet) â Text (show running-config)
CLI.entspricht(geraet, pfad, wert) â "interface Gi0/0\n ip address 10.0.0.1 255.255.255.0" | null // âentspricht:â-Zeile der GUI
CLI.anwenden(netz, geraetId, befehle) â { ok, fehler:[] } // mehrzeiliger Block ab privilegiertem Modus, fĂŒr Lösungen und Tests
CLI.vorschlag(sitzung) â nĂ€chster sinnvoller Befehl (Einstieg) | null
Ănderungen der Konsole laufen ĂŒber Modell.setzen/Verlauf (eine Quelle der Wahrheit). ping/traceroute/tracert/nslookup/ipconfig /renew rufen Sim und formatieren wie IOS bzw. Windows (.!!!!, âAntwort von âŠâ).
6.1 running-config â Anzeige und Wiedereinspielen (verbindlich, 31.10.2026)
CLI.runningConfig(geraet) liefert den Text von der ersten !-Zeile bis einschlieĂlich end (ohne die zwei Kopfzeilen, die show running-config davor setzt). Geltungsbereich: Router und Switch. Firewall: herstellerneutrale Anzeige, ausdrĂŒcklich nicht wiedereinspielbar und im Text als Kommentar gekennzeichnet. Hosts (pc/server/nas): null.
Wiedereinspiel-Invariante. Jede Zeile von CLI.runningConfig ist ĂŒber CLI.anwenden(netz, id, "configure terminal\n" + text) fehlerfrei einlesbar â ok:true, fehler:[] â inklusive !-Kommentaren und abschlieĂendem end. Die Konsole betritt keinen Konfigurationsmodus von selbst; der Wiedergabeweg beginnt ausdrĂŒcklich mit configure terminal (IOS-Weg). Nach dem Einspielen auf ein GerĂ€t desselben Typs gilt: (a) CLI.runningConfig(kopie) === text (zeichengleich, damit entspricht:- und Show-Ausgaben stabil bleiben), (b) die Modell-Kernfelder sind tiefgleich: running.if, running.ports, running.svi, running.dhcp (Pools samt leaseS, domain, reservierungen), running.acls, running.routen, running.nat, running.snooping, running.hostname, running.lines, running.banner, running.benutzer.
VLAN-Datenbank. flash.vlans gehört wie auf IOS nicht zur running-config. Sie wird separat angelegt (vlan 20, name âŠ), steht in show flash: als vlan.dat und ĂŒberlebt write erase; weg ist sie erst mit delete flash:vlan.dat.
Anzeigeform (verbindlich). Die running-config zeigt nur Zeilen, die das GerĂ€t selbst wieder annimmt; reine Komfort-Defaults ohne Befehl entfallen: version âŠ, service timestamps âŠ, boot-start-marker, boot-end-marker, no aaa new-model, system mtu routing 1500, spanning-tree mode pvst, spanning-tree extend system-id, vlan internal allocation policy ascending, duplex auto, speed auto, ip forward-protocol nd, no service pad, line aux 0. end bleibt als Blockende zulĂ€ssig.
DHCP-Pools vollstĂ€ndig: je Pool ip dhcp pool NAME, network, default-router, dns-server, lease âŠ, domain-name âŠ; je Reservierung host NAME, hardware-address âŠ, ip âŠ, danach exit (verlĂ€sst den Reservierungsmodus; nötig, weil aus (dhcp-config-host) sonst kein globales Kommando mehr erreichbar ist).
SSH (D3). ip ssh version 2 wird nicht als Befehl ausgegeben (der Parser kennt ihn heute nicht; Nachbau CLI-05/3.1). Ist ein RSA-SchlĂŒssel gesetzt, erscheint stattdessen die Kommentarzeile: ! SSH aktiv â âip ssh version 2â folgt in 3.1 (CLI-05). Das crypto key generate rsa ist wie auf IOS nicht Teil der running-config. Folge: der bisherige Assert in tests/cli-ios-ssh.test.js (âip ssh version 2â im Text) wird auf diese ehrliche Kennzeichnung umgestellt; der SSH-Zustand bleibt ĂŒber running.sshSchluessel geprĂŒft (keine AbschwĂ€chung).
DHCP-Snooping am Switch (CLI-03, 31.10.2026). running.snooping = {an: bool, vertraut: [Port, âŠ]} ist Teil der laufenden Konfiguration. Die running-config zeigt ip dhcp snooping genau dann, wenn an gilt, und je vertrautem Port ip dhcp snooping trust im interface-Block. Beide Zeilen sind ĂŒber configure terminal wieder einlesbar; die trust-Zeile Ă€ndert ausschlieĂlich vertraut, nie den globalen Schalter. ip dhcp snooping trust ist nur in (config-if) und (config-if-range) zulĂ€ssig; im globalen Konfigurationsmodus antwortet die Konsole mit Fehler und Hinweis auf den Schnittstellenmodus. no ip dhcp snooping trust entfernt den Port, no ip dhcp snooping schaltet global aus und lĂ€sst die Portliste stehen.
show ip dhcp snooping (nur Switch) zeigt âSwitch DHCP snooping is enabled|disabled", âDHCP snooping is configured on following VLANs: 1", die Tabelle der vertrauten Ports (âInterface"/âTrusted") und die Zeile âDHCP snooping verworfen (seit dem letzten Neustart)" je Port. Die ZĂ€hler stammen aus dem Laufzeitspeicher netz.zustand[id].snooping[port].verworfen (src/sim/switch.js:64-69, § 5.4) und sind ausdrĂŒcklich Laufzeitdaten, kein Konfigurationszustand â deshalb stehen sie nicht in der running-config. Bei ausgeschaltetem Snooping nennt die Ausgabe den Befehl zum Einschalten. Ehrliche Grenze: Das Modell bildet Snooping nur ĂŒber das Port-Vertrauen ab, nicht ĂŒber eine VLAN-Zuordnung; echte Cisco-Hardware wurde nicht geprĂŒft. Die Roundtrip-Invariante aus § 6.1 gilt unverĂ€ndert.
6.2 Einleseformen und Grenzen (verbindlich, 31.10.2026)
Geheimnisse. Typ-PrĂ€fixe 0 (Klartext), 5 (MD5-artig), 7 (verschleiert) werden beim Einlesen akzeptiert und unverĂ€ndert wieder angezeigt â fĂŒr enable secret, username ⊠secret|password und password in line. Gespeichert wird die eingegebene Form; reine Klartext-Eingaben erscheinen weiterhin in der bisherigen Anzeigeform (Typ 5 fĂŒr secret, Typ 0/7 fĂŒr password). Erneutes Einlesen erzeugt dieselbe Anzeige.
no ip domain lookup â Anzeige und Befehl sind deckungsgleich; die running-config schreibt die Tokenform des Befehls (no ip domain-lookup), nicht die IOS-Kosmetik mit Leerzeichen.
CLI.anwenden(netz, id, text, {verlauf?}) â Vertrag unverĂ€ndert: beginnt im privilegierten Modus (#), fĂŒhrt !-Kommentarzeilen und Leerzeilen aus, beantwortet nur automatische RĂŒckfragen (Enter/confirm) selbst, liefert {ok, fehler:[{nr, zeile, meldung}]}. Ein Block, der einen Konfigurationsmodus braucht, beginnt mit configure terminal.
Nicht wiedereinspielbar (Grenze, ehrlich): Firewall-Konfiguration (herstellerneutral) und alles, was nur im Inspektor konfigurierbar ist. Hosts haben keine IOS-running-config. Die show running-config-Kopfzeilen gehören nicht zum Vertragstext.
Vertrags-Test: tests/cli-runningconfig.test.js (Roundtrip Router/Switch/Startup, Zustandsvergleich, Phantomliste, absichtlich verlorenes Feld â rot).
7 · Spiel (Spiel)
7.1 Ticket (Daten in daten/tickets-*.js â DATEN.tickets)
{ id:"salon-01", art:"stoerung"|"projekt"|"wartung"|"mini", stufe:"E"|"AP1"|"AP2", karriere:1,
kunde:"salon", titel:"Drucker nicht erreichbar", briefing:"Hallo, âŠ", symptom:"Kasse druckt nicht",
skills:["lab.gateway"],
netz:(z) => Netz, // Fabrik; z = Zufall(seed) fĂŒr Varianten; liefert das GESUNDE oder Start-Netz
fehler:[{ injektor:"gw-falsch", auf:"pc-kasse", param:{} }], // optional, auf das Netz angewendet
ziele:[ { typ:"erreichbar", von:"pc-kasse", nach:"drucker", proto:"icmp"|"tcp"|"http"|"dns", port:null, text:"Kasse erreicht Drucker" },
{ typ:"blockiert", von:"gast", nach:"server", proto:"tcp", port:445, text:"GĂ€ste kommen nicht an die Dateifreigabe" },
{ typ:"dhcp", von:"pc2", text:"PC2 bekommt per DHCP eine Adresse" },
{ typ:"konfig", geraet:"sw1", pfad:"ports.Fa0/5.accessVlan", wert:20, text:"âŠ" },
{ typ:"gespeichert", geraet:"r1", text:"Konfiguration gesichert" } ],
hilfen:{ frage:["âŠ"], bereich:[{geraet:"pc-kasse"}], konkret:["âŠ"] },
loesung:[ { geraet:"pc-kasse", setzen:{ "if.eth0.gw":"192.168.1.1" }, text:"Gateway korrigieren" },
{ geraet:"r1", cli:"conf t\ninterface Gi0/1\nno shutdown\nend", text:"Schnittstelle einschalten" } ],
erklaerung:"âŠ", quelle:"Network â Lernfassung", lohn:{ euro:40, ruf:1 } }
Pflicht (Test): Jede Lösung erfĂŒllt alle Ziele; das Start-Netz verletzt mindestens ein Ziel, und zwar mit dem erwarteten Grund.
7.2 Spielstand (store "labor", v:3)
Der Lebenszyklus der Wiederholungs-Tickets im Postfach steht in § 7.5.
{ v, euro, ruf, stufe, kunden:{[id]:{vertrag, ampel, seit, sterne:[]}}, postfach:[TicketInstanz], aktiv:iid|null, erledigt:[{id, sterne, tag, hilfe}], playbooks:{slots, aktiv:[skill]}, tag:{âŠArbeitstagâŠ}, zuletzt:ms, einstieg:{âŠ} } (Die Liste ist ein Auszug; weitere Felder u. a. naechsteIid, buch, angebot, dex, tagesraetsel, tagebuch. Die Einstellungen liegen NICHT hier, sondern unter dem eigenen Store-SchlĂŒssel "einst" â st.einst gibt es nicht.)
TicketInstanz = { iid, ticketId, seed, netz, hilfeStufe, hilfen:[], start, frist|null, quelle:"postfach"|"wartung"|"wiederholung"|"generiert"|"notfall"|"raetsel"|"pruefung"|"folge", vielfalt?, kuratiert?, kuratiertId?, klingelt? }
Onboarding, nachgetragen 06.10.2026 (spiel/erstestunde.js): st.einstieg = { fertig, coach:{}, begruessung?:"auftrag"|"umsehen", abschluss?:true }. begruessung setzt die Wahl der BegrĂŒĂungskarte â genau einmal, und nur in einem frischen Stand (!fertig und kein erledigter Auftrag). abschluss markiert die gezeigte Abschluss-Station und verlangt begruessung und fertig. Fehlt einstieg ganz oder steht fertig: true, sieht ein Altstand nichts davon (die BegrĂŒĂung entfĂ€llt, der Abschluss ebenso).
Auftragsvielfalt (neu 06.10.2026, spiel/postfach.js · spiel/generator.js · daten/basis.js)
Vorher war jeder handgeschriebene Auftrag starr: def.netz ignorierte den Instanz-Seed, das Netz war bei Seed 1 und Seed 987654321 in 46 von 46 FĂ€llen byte-gleich. Jetzt:
def.fuerSeed(seed)liefert die Fassung je Instanz-Seed â Adressen, Ziele, Lösung und Hilfen werden mit dem Seed gebaut und je(id, seed)gemerkt (†32 Fassungen je Ticket). Alle Zufallsströme kommen ausZufall(seed); ohne Seed bleibt die Definition byte-gleich wie vorher.def.fehlerstellen = [{injektor, stelle}]â wo keinziel/wahlfestgeschrieben ist, wĂ€hlt der Seed die Fehlerstelle; derselbe Fehler kommt nie zweimal im selben Auftrag vor.Spiel.ticketGueltig(def)ist die SelbstprĂŒfung jeder Seed-Fassung (Startbruch mit erwartetem Grund, Lösung heilt, Vorlagen-Regression). FĂ€llt eine Fassung durch, bleibt die feste Fassung spielbar â eine gewĂŒrfelte Variante darf nie unlösbar werden.- Ausnahmen bleiben fest: Terminal-AuftrĂ€ge (
spec.art === "terminal") und alles, was konkrete Werte in Lösung oder Befehlsmuster nennt, laufen weiter auf dem festen Netz. - Spielstand: neue Instanzen tragen
vielfalt: true. Eine Instanz ohne dieses Feld (Altstand) behĂ€lt ihre feste Fassung â ein laufender Auftrag wechselt sein Netz nie. Gemessen: Altstandsalon-02, Netzkennwertee287645:12368vor und nach dem Umbau gleich, Auftrag spielbar.vbleibt2. - Erreicht: 58 HandauftrĂ€ge Ă 12 Seeds = 627 verschiedene Netze (vorher 58), gleicher
(id, seed)in 696/696 Paaren identisch, Massendurchspiel 696/696 grĂŒn.
7.3 Spiel-API (Auswahl)
Spiel.laden(), Spiel.st (Zustand), Spiel.postfach(), Spiel.oeffnen(iid), Spiel.abnahme(inst) â [{ziel, ok, grund, trace}] + Sterne, Spiel.hilfe(inst) â nĂ€chste Stufe, Spiel.vorfuehren(inst) â Lösungsschritte, Spiel.tick(ms) (Idle: Wartung, Einkommen, Ampeln), Spiel.offlineBericht(), Spiel.mini() â Mini-Ticket, Spiel.generiere(skill, seed) (Injektor-Generator), Spiel.INJEKTOREN.
Bus-Ereignisse: netz-geaendert, ticket-neu, ticket-geloest, zustand-geaendert, trace, modus (leiste/voll), hilfe.
7.4 Speichern â verbindlicher Vertrag (06.10.2026)
| Punkt | Vertrag |
|---|---|
| SchlĂŒssel | labor (Spielstand), einst (Einstellungen), sandbox (freies Labor), labor-sicherung (rollierende Zweitsicherung), klassenraum (Klassenraum-Sitzungen, fassung: 2 seit 3.0.0 â Nachtrag 09.10.2026, § 12.1). Geschrieben wird der ganze Speicher als ein JSON unter dem einen SchlĂŒssel netzwerk-labor â im Browser und in der Android-Fassung in localStorage, im Desktop-Programm ĂŒber Tauri in spielstand.json. Neue SchlĂŒssel brauchen keine Ănderung an kern/basis.js â die Speicher-Map ist frei |
| Entprellung | genau eine: SPEICHER.entprellung = 1500 ms (kern/basis.js). Spiel.speichern() markiert nur âschmutzig"; geschrieben wird 1500 ms nach der letzten Ănderung. Spiel.AUTOSPEICHERN_MS = 0 â die frĂŒhere zweite Schicht (400 ms in spiel/ticket.js) ist entfallen |
| Sofort schreiben | store.sofort(); im Spiel Spiel.sofortSpeichern() (RĂŒckgabe immer erfĂŒllt â ein Fehler ist bereits gemeldet). Aufrufer: Auftragsabschluss spiel/abnahme.js (bestanden und nicht bestanden), Ticket öffnen spiel/ticket.js, KĂ€ufe spiel/wirtschaft.js · spiel/werkzeuge.js · spiel/playbooks.js, Meilensteine spiel/karriere.js (Aufstieg, Fest gesehen), App in den Hintergrund ui/start.js (pagehide, beforeunload, visibilitychange â hidden) |
| Kennzeichen | store.stand() â {art:"schreibt"\|"gesichert"\|"fehler", fehler, zeit}. Der Bus meldet jede echte Ănderung als speicher-stand, ein Fehler zusĂ€tzlich als speicher-fehler. Die Kopfzeile zeigt daraus â⊠sichert / â gesichert / â nicht gesichert" (ui/app.js, role="status", kein Klickziel, keine Animation; unter 380 px Breite Kurzform) |
| Fehler | Ein gescheiterter Schreibvorgang lĂ€sst schmutzig stehen (der nĂ€chste Versuch nimmt den Stand mit), fĂŒllt SPEICHER.fehler und meldet ihn ĂŒber den Bus; der Entpreller fĂ€ngt seine eigene Ablehnung ab. Vorher blieb ein voller Speicher stumm |
| Sicherung | Spiel.SICHERUNG = "labor-sicherung", Inhalt {v, zeit, stand}. Angelegt vor einer Migration (v < Spiel.VERSION) und vor einem Import (Spiel.sicherungAnlegen(roh, true)); eine frische Sicherung bleibt Spiel.SICHERUNG_FRISCH_MS = 60 s unangetastet, damit die Migration nach dem Neuladen die Import-Sicherung nicht ersetzt |
| Import | Spiel.importPruefen(d): fremdes Format (format !== "netzwerk-labor" oder fehlendes speicher) und zu neue StĂ€nde (labor.v > Spiel.VERSION) werden abgewiesen; alte StĂ€nde und StĂ€nde ganz ohne v-Feld sind erlaubt â die Migration holt sie herein. GeprĂŒft wird die Schemaversion, nicht die Programmversion der Datei |
| Nicht im Stand | Laufzeit je Instanz (Spiel._lz: Verlauf, Startnetz, Ziele, letzteArbeit) und alles, was ui/ nur anzeigt. Ein Neuladen holt den Stand zurĂŒck, nicht die Sitzung |
Gemessen (06.10.2026, Edge headless, Einzeldatei-Fassung, 1366Ă768): Spiel.speichern() 2010 â 1512 ms bis zum Schreibvorgang, Netzwechsel 2415 â 1515 ms, Auftragsabschluss 2436 â 6,7 ms (Gegenprobe des PrĂŒfers: 22 ms), Kauf 3,8 ms (PrĂŒfer: 24 ms), Verstecken der Seite 0 â 1 Schreibvorgang. Spielstand: 26.554 B beim Start, 27.420 B nach einem Abschluss, 102.799 B bei 30 Postfach-Instanzen. Grenze: ein Prozess-Tod ohne hidden-Wechsel verliert die letzten †1500 ms.
Sicherung wiederherstellen (3.0.0, SPL-02). Spiel.sicherung() liefert {v, zeit, stand} aus dem SchlĂŒssel labor-sicherung oder null. Neu ist der RĂŒckweg:
Spiel.sicherungWiederherstellen()prĂŒft die Sicherung: fehlt sie â{fehler:"keine-sicherung", grund};standfehlt/unbrauchbar â{fehler:"stand", grund};v > Spiel.VERSIONâ{fehler:"fassung", grund}. In allen FehlerfĂ€llen bleibt der laufende Stand unverĂ€ndert und die Sicherung erhalten.- Bei Erfolg:
store.set("labor", stand)âSpiel.laden()(migriert alte StĂ€nde) âSpiel.sofortSpeichern()â Sicherung verbrauchen (store.set(Spiel.SICHERUNG, null)); RĂŒckgabe{ok:true, v, zeit}. Der laufende Spielstand wird ersetzt; die Einstellungen untereinstbleiben. - Einmal-Verbrauch: eine zweite Wiederherstellung meldet âkeine Sicherung" (sonst spielte ein spĂ€terer Start erneut einen Altstand ein).
- Anzeige/Aufrufer: Knopf im Import-/Spielstand-Abschnitt (
src/ui/karriere.js, SPL-02b) mit Datum ausSpiel.sicherung().zeit; Test intests/spiel-sicherung.test.js(Stand A â Stand B importieren â Sicherung liefert A â Wiederherstellen setzt Euro/Ruf/erledigt/postfach auf A und schreibtlabor; zweiter Ruf âkeine Sicherung").
Spielstand-Migration 2.9.0 â 3.0.0 (neu 3.0.0, SPL-10). Die Schemaversion des Spielstands bleibt labor.v = 3: ein echter 2.9.0-Stand trĂ€gt v:3 und wird unverĂ€ndert ĂŒbernommen (unbekannte Felder bleiben erhalten). Ăltere StĂ€nde (v < 3 oder ohne v) migriert Spiel.laden(); vor der Migration legt laden die Sicherung labor-sicherung an (bestehendes Verhalten, siehe oben), fehlende Pflichtfelder ergĂ€nzen migrieren und die Spiel.ergaenzer-Kette, unbrauchbare EintrĂ€ge (z. B. postfach/kunden) werden verworfen statt zu werfen. Ein ganzer 2.9.0-Export (format:"netzwerk-labor", speicher) wird ĂŒber store.initialisieren + Spiel.laden() ĂŒbernommen; ein darin enthaltener klassenraum-Altstand (fassung 1) wird beim Lesen nach fassung 2 migriert (KLR-01a). Eine im Export enthaltene labor-sicherung bleibt Sicherung und wird nicht der laufende Stand. FehlerfĂ€lle: labor.v > Spiel.VERSION weist Spiel.importPruefen ab; ein Stand ganz ohne v gilt als v:0 und wird migriert (kein stiller Verlust). Abnahme: tests/spielstand-migration.test.js mit â„ 8 Zusicherungen (v:3 unverĂ€ndert, v:1âv:3 mit Sicherung, kaputte Felder repariert, unbekannte Felder bleiben, Klassenraum fassung 1â2, zu neuer Stand abgewiesen) + Gegenprobe: fehlt in einer Kopie ein Pflichtfeld, wird die PrĂŒfung rot.
7.5 Postfach-Lebenszyklus (3.0.0, SPL-01) â verbindlicher Vertrag (31.10.2026)
Tickets mit quelle:"wiederholung" entstehen an zwei Stellen: Spiel.tag.bereitschaftStarten() (Tages-Bereitschaft) und Spiel.wiederholungAnlegen() (nach âLösung vorfĂŒhren"). Verbindlich:
- Dedup je Fertigkeit: je Fertigkeit (
inst.gen.skillbzw. erstedef.skills) bleibt höchstens ein offenes Wiederholungs-Ticket; ist schon eines offen, legt keiner der beiden Erzeuger ein zweites an. - Verfall: ein offenes Wiederholungs-Ticket, dessen
startÀlter alsSpiel.TAG.WIEDERHOLUNG_VERFALL_TAGE = 7Kalendertage ist, wird entfernt. - Obergrenze: höchstens
Spiel.TAG.BEREITSCHAFT_MAX = 3offene Tickets mitquelle:"wiederholung"; darĂŒber werden die Ă€ltesten entfernt. - AufrĂ€umorte:
Spiel.postfachAuffuellen()(lÀuft auch inSpiel.laden()),Spiel.tag.bereitschaftStarten()(vor Neuanlage) undSpiel.wiederholungAnlegen(). Beim Entfernen wirdSpiel._lz[iid]gelöscht,st.aktivgeleert, falls betroffen, undst.tag.bereitschaftauf lebende iids bereinigt. - AltstÀnde: ein 2.9.0-Spielstand mit vielen Wiederholungen wird beim nÀchsten
Spiel.laden()bereinigt (†3, †1 je Fertigkeit); sonst Ă€ndert das AufrĂ€umen nichts am Stand. - GröĂe/Latenz (Abnahme): 10 simulierte Tage ergeben †3 sichtbare Wiederholungen,
JSON.stringify(Spiel.st).length < 120000undstore.set("labor", Spiel.st)im Median †2 ms (Zielwert < 1 ms; Lead-Beschluss 31.10.2026, weil CI-Maschinen langsamer sein können).
8 · Plattform
Plattform = { name:"tauri"|"browser", version,
impl, // beim ersten Zugriff gewÀhlt: Tauri nur bei window.__TAURI__,
// sonst Browser (src/plattform/plattform.js:6-9)
speichern(daten) â Promise, // Tauri: invoke("speichern", {json}), atomar + Sicherungen
fenster:{ modus(m), immerOben(b), position(ecke), groesse(b, hoehe), zeigen(), verstecken(),
beenden(), zustand() },
abzeichen(n, text),
autostart(b) â Promise, autostartStatus() â Promise,
datei:{ exportieren(name, text) â Promise, importieren() â Promise<String|null> },
kann(funktion) â { ja, grund }, // FĂ€higkeitsmatrix, siehe unten
an(ereignis, fn),
info() â { os, sitzung, kann?, gruende?, selbsttest? } }
FĂ€higkeitsmatrix (KRN-02, verbindlich). Die acht FĂ€higkeiten sind immerOben, tray, autostart, hotkey, leiste, klickdurch, vollbildErkennen, benachrichtigen. kann(f) liefert immer {ja:boolean, grund:string|null}:
- Tauri: ausschlieĂlich aus
window.__LABOR_PLATTFORM__.kann/gruende(Rustfenster::info,shell/src-tauri/src/fenster.rs:489-519); fehlt die Ermittlung, ist die Antwortja:falsemit Grund; eine unbekannte Funktion istja:false(âDiese Funktion kennt das Labor nicht."). - Browser: alle acht
ja:falsemit je einem Satz Grund; unbekannt ebenfallsja:false. - Kein Adapter antwortet âim Zweifel ja" (Befund KRN-B07).
benachrichtigenist als FĂ€higkeit gefĂŒhrt, die Funktion ist nicht gebaut: beide Adapter meldenja:falsemit Grund; der Einstellungs-Schalter ist dadurch deaktiviert und nicht wirkungslos.
Ereignisse (Tauri): modus-extern, zweiter-start, ruhe, labor-beenden sowie tray-klick, hotkey, oeffnen â die HĂŒlle emittiert die drei letzteren selbst mit der Quelle als Ereignisnamen (emit(quelle) in fenster::umschalten, shell/src-tauri/src/fenster.rs:327); src/plattform/plattform-tauri.js reicht sie an die Seite. Die Abos in src/ui/start.js:102-105 sind damit keine toten Abos mehr (KRN-02, vorher Befund KRN-B06).
Rust-Befehle (invoke): speichern{json}, fenster_modus{modus}, fenster_groesse{b,h}, fenster_ecke{ecke}, fenster_zustand, fenster_knopf, immer_oben{an}, abzeichen{n,text}, autostart{an}, autostart_status, exportieren{name,inhalt}, importieren, plattform_info, ruhe{an}, beenden, selbsttest_ergebnis{json} (shell/src-tauri/src/main.rs:154-155).
Datei-API (KRN-04, verbindlich). importieren() löst in beiden Adaptern immer auf (String|null); Abbruch = null (Browser: cancel des Dateifelds; Tauri: Dialog-Abbruch). exportieren() ist nur true, wenn der Weg erfolgreich war (Tauri: Ok(true); Browser: Blob/URL/Klick ohne Fehler â ein echter Download ist dort nicht belegbar); Fehler/Abbruch = false, Tauri meldet zusĂ€tzlich datei-fehler {art, fehler} auf dem Bus. Die frĂŒheren toten Exporte Synchron-Laden und Systembenachrichtigung sind mit KRN-02 entfernt (0 Aufrufer, 0 Exporte).
9 · OberflÀche (UI)
- Ein Fenster, zwei Ansichten:
UI.modus("voll"|"leiste"). Vollansicht: Kopfzeile (Euro, Ruf, Stufe, Postfach-ZĂ€hler) · Andock-Leiste links (Postfach · Labor · Kunden · Wiki · Lernstand · Shop) · ArbeitsflĂ€che. Beim Wechsel zur Leiste wird das DOM der Vollansicht abgebaut. - Labor: GerĂ€teleiste · SVG-FlĂ€che · Inspektor rechts · Simulation unten · Auftragsleiste oben (live â).
- Zeigerereignisse, kein HTML5-Drag-and-Drop. Farben immer mit Text/Symbol.
prefers-reduced-motionrespektieren. Hell- und Dunkelmodus.
9.1 Labor seit Ausbau 1.2 (Phase A âAufrĂ€umenâ)
- GerÀte-FÀcher (
ui/editor-fach.js, PrĂ€fixpa-):UI.KATEGORIEN = [{id, titel, kurz, typ, skin, taste, text}](endgeraete · server · netzwerk · aussen; Platz fĂŒr drahtlos/zubehoer), jedesUI.GERAETE-Element trĂ€gtkategorieundkurz. Eine Kategorie öffnet ein Fach daneben, höchstens eins offen. Tasten:1â4Fach, im offenen Fach1âŠnGerĂ€t,Esczu. Zuletzt benutzt:store "einst"âlabor.zuletzt = [{typ, skin}](max. 3). Platzieren ist ein Zwischenwerkzeug: danach gilt wieder das Werkzeug davor. - Ebenen sind ein Ansicht-MenĂŒ (
Umschalt+1âŠ5).UI.ebenen.setzen(id, {merken})âmerken:falsefĂŒr Ticket-Vorgaben;UI.ebenen.gemerkt()(eigene Wahl, Freies Labor);UI.ebenen.fuerSkills(skills)â Standardansicht eines Tickets (erster Skill mit eigener Ebene, sonstip). UI.labor.laden(netz, {titel, verlauf, auftrag, ebene?, ansichtMenue?, sandbox?}):ebenesetzt die Ansicht ohne sie zu merken,ansichtMenue:falseblendet das MenĂŒ aus (EinstiegsauftrĂ€ge). Nachladensind Inspektor und Simulation unsichtbar, bis zur ersten Auswahl bzw. ersten Aufzeichnung (die das Panel einmal öffnet). Neu:UI.labor.einsetzen(typ, skin)(Mitte der FlĂ€che).- Auftragszeile + Auftragsmappe (
ui/spiel.js, PrĂ€fixam-): eine Zeile (Kunde · Titel ·Ziele x/y· Auftrag lesen · Abnahme · âŻ), Mappe mit Reitern Brief/Ziele klappt beim ersten Ăffnen einer Instanz einmal auf (nicht im Coach-Auftrag, nicht in der PrĂŒfung). Hilfe steckt im ââŻâ-MenĂŒ. Genau ein.primaerje Zustand: hat ein offenes Feld (Mappe, Hilfeleiter) einen eigenen Hauptknopf, verliert die Abnahme ihre Hervorhebung. - Eine Quelle fĂŒr Hinweise: Ein Element mit
data-hinweisquellein der Auftragszeile (Coach) schaltet den Werkzeug-Hinweis der FlÀche stumm. Toasts: höchstens einer gleichzeitig; ein verdrÀngter Toast mit Aktion kommt einmal nach. - Lehrtexte auf Klick:
UI.erklaeren(code, niveau)(PrĂ€fixex-) zeigtDATEN.lehrtexte[code]erst auf âErklĂ€r mir dasâ, dann auf Wunsch ausfĂŒhrlicher (AP1 â E), zuletzt die Quelle. - Inspektor: Reiter beginnen mit âĂbersichtâ; Terminal/Konsole ist ein Knopf im Kopf (
K.reiter = "konsole"bleibt der interne Zustand,UI.inspektor.reiter(c, "konsole")gilt weiter).
9.2 Welle 1, Sitzung S1 âSofortgefĂŒhlâ (Design â SpielspaĂ 2.0)
Spiel.regeln(inst) â { niveau, warnungen:bool, liveHaken:bool, liveGrund:bool, versuchAbzug:0|0.5|1 }
// Stufenregeln (Design § 6): E: Warnungen an, Haken mit Grund, Versuche frei
// AP1: Warnungen erst ab Hilfestufe 2, Haken ohne Grund, ✠â
ab dem 2. Abnahmeversuch (einmalig)
// AP2: keine Warnungen, Haken erst bei der Abnahme, â1 â
ab dem 2. Versuch (einmalig). PrĂŒfung: wie AP2, ohne Abzug.
inst.abnahmen // Zahl der Abnahmeversuche (gab es schon); Spiel.abnahme zÀhlt hoch
Spiel.szene(inst, abnahme) â [ { ziel, art:"druckt"|"seite"|"adresse"|"gesperrt"|"haken"|"sicherung",
pfad:[geraeteId], von, ende:geraeteId, text } ] // headless, aus der Trace der Abnahme
Spiel.geaenderteGeraete(inst) â [geraeteId] // Diff gegen das Startnetz: Konfig, Strom, startup, Kabel, neu
Spiel.tonPegel(ton, {modus, fokus}) â 0..1 // "aus"â0; Leiste oder ohne Fokus â 0; "leise" 0.18, "normal" 0.4
store "einst".ton = "aus"|"leise"|"normal" // Standard "leise" (Spiel.EINST_STANDARD)
Spiel.postfachZiel() â 2 | 3 // bis zum 2. erledigten Auftrag zwei Angebote verschiedener Kunden (erste Wahl), dann 3
- Funktionsprobe (
ui/szene.js, PrĂ€fixsz-): nach bestandener Abnahme 2â6 s im Labor â geĂ€nderte GerĂ€te pulsieren, je Ziel (max. 4, AP1/AP2 max. 2) fĂ€hrt ein Paket den Pfad, das EndgerĂ€t reagiert, der Kunde spricht an seinem GerĂ€t. Ăberspringbar (Esc, Klick, Leertaste); bei reduzierter Bewegung eine Haken-Liste (1,2 s). Danach der Ergebnisdialog mit höchstens drei Blöcken (.sp-block): Sterne + Lohn · Dein Weg · Merke. UI.labor.laden(âŠ, {warnungen: () => bool}): steuert die â!â-Warnungen auf der FlĂ€che und im Inspektor (Kopf-Chip, Abschnitt Hinweise, gelbe Feldwarnungen). Ohne Angabe: an.- SpielgefĂŒhl:
UI.juice(el, art)setzt kurz die Klassejc-<art>(nichts bei Bewegung âausâ);UI.klang.spielen(name)(ui/klang.js, WebAudio, keine Dateien) âtrue, wenn hörbar gespielt. - BĂŒhne frei?
UI.buehneFrei()â Abzeichen, Kundenpost und Aufstiegsfeier warten, solange ein Dialog (.sp-overlay) oder die Funktionsprobe (.sz-buehne) lĂ€uft.UI.toast.zu(id)schlieĂt eine Meldung. Einstieg:UI.spiel.mess={t0, ersteHandlung, erfolg}(ms seit Seitenstart).
9.3 Welle 1, Sitzung S2 âBogenâ (Hub, TagesrĂ€tsel, Fehlerdex, Spieltagebuch)
Spielstand v:2 (Migration: fehlende Felder bekommen ihren Standardwert, ein v:1-Stand lĂ€dt unverĂ€ndert weiter). Aktueller Stand ist v:3 â siehe § 13 (Feld training, 07.10.2026).
st.dex = { [injektor]: { gesehen:"2026-10-04", verstanden:null|"2026-10-05" } }
// gesehen = Auftrag mit dieser Fehlerart bestanden; verstanden = bestanden ohne bezahlte Hilfe (Hilfestufe < 4).
// Erst beim Abschluss erfasst â ein offener Auftrag verrĂ€t seine Ursache nie ĂŒber den Dex (R1).
st.tagesraetsel = { serie:0, [tag]: { nr, niveau, sterne, sek, hilfe, versuche, ziele:["g"|"y"|"w"], zeile } }
st.tagebuch = [ { t, art:"sitzung", bis } | { t, art:"auftrag-ende", id, form, quelle, niveau, sek, sterne, hilfe, versuche, weiter:null|bool } ] // höchstens 500
st.tag.aufwaermen = 0..n // Mini-Karten im AufwÀrmen heute
inst.zielErst = [ versuchNr|null ] // je Ziel: in welchem Abnahmeversuch zuerst erfĂŒllt (Spiel.abnahme)
inst.quelle = ⊠| "raetsel" // TagesrÀtsel: nicht im Postfach, verfÀllt am Folgetag
Spiel.dex.liste() â [ {id, titel, gruppe, zustand:"unbekannt"|"gesehen"|"verstanden", ab:stufe, symptom, erkennen:[text], erklaerung, quelle, gesehen, verstanden} ]
Spiel.dex.gruppen() â [ {id, titel, ehrentitel, ids:[injektor], verstanden, gesamt, fertig} ]
Spiel.dex.erfassen(inst, def) â {neu:[{id, titel, zustand}], titel:[ehrentitel]} // aus Spiel.abschliessen
Spiel.tagebuch.sitzung() · .auftragEnde(inst, def, abnahme) · .weiter() · .aktiv() · .auswertung() â Text (zum Kopieren)
Spiel.raetsel.nummer(tag) · .def(tag, niveau) · .heute() · .starten() â inst · .teilen(tag) â Text
Spiel.hub.stand() â { tag, serie:{tage, urlaub, frei, zurueck}, naechster, aufwaermen:{ziel, erledigt}, raetsel, post, tagesziel, feierabend:null|{bilanz, ausblick} }
Spiel.hub.serie(tageSet, heute) â { tage, urlaub, frei } // Serie mit Urlaubstagen: 2 Tage je Kalenderwoche fehlen erlaubt
- TagesrÀtsel: Fertigkeit und Netz hÀngen nur an Datum und Niveau:
Spiel.generiere(skill, seed, {stufe})mitskill = Zufall("raetsel:"+tag+":"+niveau).wahl(Spiel.RAETSEL.SKILLS[niveau])und festem Seed je Tag/Niveau â alle in der Klasse bekommen dasselbe Netz, ohne Server. Nummer = Tage seit 01.10.2026 + 1. Teilen-Text verrĂ€t die Ursache nicht (đ© erster Versuch, đš spĂ€terer, ⏠nicht erreicht â je Ziel). - Hub âHeuteâ (
ui/hub.js, PrĂ€fixhb-): Startansicht (auĂer im allerersten Auftrag). Datumszeile + Serie, eine Karte âDein nĂ€chster Auftragâ mit dem einzigen Hauptknopf, drei Kacheln (AufwĂ€rmen · TagesrĂ€tsel · Post bzw. Fehlerdex), nach dem Tagesziel Feierabend mit Bilanz und einem Ausblick auf morgen. Platzbudget: †40 Wörter, 1 Hauptknopf. - Fehlerdex im Lernstand (Abschnitt
#dex); Spieltagebuch zum Kopieren in Einstellungen und Lernstand.UI.kopieren(text) â Promise<bool>(Zwischenablage mit RĂŒckfall).
9.4 Welle 2, Phase B âNetzplan, Dock, Akte, Verdachtâ
Spiel.plan.sollNetz(inst) â Netz // Startnetz + Referenzlösung = so soll es sein (Störung: gesund, Projekt: Ziel). Nie mit Fehler.
Spiel.plan.aus(netz, {art, verdeckt}) â Plan
Plan = { art:"netzplan"|"skizze"|"tabelle", breite, hoehe,
knoten:[ {id, name, typ, skin, x, y, ebene, adressen:[{port, ip, maske, vlan}], zeigen:[text]} ],
linien:[ {a:{id, port}, b:{id, port}, trunk:bool, vlan:null|n} ],
netze:[ {cidr, vlan, geraete:[id]} ],
tabelle:[ {id, name, port, ip, maske, gw, dns, vlan, dhcp:bool} ], // verdeckte Felder = "?"
verdeckt:[ {id, feld} ] }
Spiel.plan.art(inst) â "netzplan" (E) | "skizze" (AP1, Tabelle daneben) | "tabelle" (AP2, Gateway/DNS der Rechner verdeckt) | ticket.plan.art
Spiel.plan.fuer(inst) â Plan (gecacht je Instanz)
Spiel.plan.diff(a, b) â [ {geraet, pfad, soll, ist} | {geraet, kabel:"fehlt"|"zuviel", gegen} ] // Konfig (running), Strom, VLAN-Datenbank, Kabel
Spiel.plan.abweichungen(inst) â [geraeteId] // Plan â Labor, fĂŒr âAbweichungen markierenâ (kostet wie Hilfestufe 4)
inst.akte = [ {n, t, art:"ping"|"trace"|"kabel"|"befehl"|"plan", von, nach, befund, schicht:null|1..7, grund:null|code, ok, wichtig:false} ]
Spiel.akte.ausPing(inst, {von, nach, ergebnis}) · .ausKabeltest(inst, {kabel, oben, grund}) · .stern(inst, n) · .liste(inst)
// befund = was ein echtes Werkzeug zeigt (âZeitĂŒberschreitungâ, âZielhost nicht erreichbarâ); Grund/Schicht nur im Einstieg sichtbar
inst.verdacht = { schicht, ursache:injektor, geraet, t, vorEingriff:bool, treffer:null|"schicht"|"ursache"|"voll" }
Spiel.verdacht.optionen(inst) â { schichten:[{n, name, text}], ursachen:[{id, titel, schicht}] (4 Fehlerarten, eine richtig), geraete:[{id, name}] } | null
Spiel.verdacht.setzen(inst, {schicht, ursache, geraet}) · .bewerten(inst) â {treffer, gesetzt, vorEingriff, richtig:{ursache, schicht, geraete}, text}
// Ursachen sind Fehlerarten (= Fehlerdex-EintrĂ€ge), nicht SimulationsgrĂŒnde: âFalsches Standardgatewayâ statt âGateway antwortet nichtâ
// E freiwillig (+10 % Lohn bei vollem Treffer vor dem Eingriff), AP1 ohne Verdacht ✠â
, AP2 â1 â
(Spiel.regeln(inst).verdacht)
st.werkzeuge = { kabeltester:bool, netzpruefer:bool } // Shop âWerkzeugeâ; inst.netzpruefer = true schaltet die â!â im AP-Niveau zu
- Dock (
ui/editor.js, PrĂ€fixlb-dock): ein Bereich rechts mit Reitern Inspektor · Simulation · Plan · Akte (Terminal folgt in C). Ein Reiter erscheint erst, wenn er Inhalt hat; ist keiner da, gibt es kein Dock (R5). Breiteclamp(260px, 40% â 72px, 440px)der Laborbreite â die FlĂ€che behĂ€lt â„ 60 %. Die Simulation liegt nicht mehr unten.UI.labor.dock(reiter|null),UI.labor.dockReiter(id, {titel, symbol, verfuegbar});inspektor(zu)undsimulation(zu)gelten weiter. - Netzplan (
ui/netzplan.js, PrĂ€fixnp-): Reiter âPlanâ im Dock und in der Auftragsmappe; Zeichnung automatisch in Ebenen ausgerichtet (Internet â Router/Firewall â Switches â EndgerĂ€te), Schrift â„ 14 px, Tabelle daneben. Klick auf ein Plan-GerĂ€t hebt es im Labor hervor und wĂ€hlt es. - Akte (
ui/akte.js, PrĂ€fixak-): Reiter âAkteâ â Verdacht oben, darunter Beweiskarten (neueste zuerst, â markiert).
9.5 Welle 2, Phase C âTerminalâ
Modell.osVon(geraet) â "windows" | "linux" | "ios" | null // pc â windows, server/nas â linux (geraet.os ĂŒbersteuert), switch/router/firewall â ios
CLI.sitzung(netz, id, {verlauf, einstieg, tipps:"alle"|"fehler"|"keine"}) // Tipp-Zeilen: Einstieg alle, AP1 nur nach Fehlern, AP2 keine
CLI.eingabe(s, zeile) â { âŠ, befund?:"eine Zeile fĂŒr die Akte", ok?:bool } // Diagnosebefehle (ipconfig, ping, nslookup, ip a, dig, systemctl status âŠ)
CLI.host.* // gemeinsame Helfer (cli/host-terminal.js); Befehle: cli/host-windows.js, cli/host-linux.js
CLI.traceMerken(netz, trace) // letzte Aufzeichnung je Netz â tcpdump zeigt sie
inst.befehle = [ {t, geraet, befehl, ok} ] // ausgefĂŒhrte Terminalbefehle (fĂŒr Zielart âbefehlâ, Abzeichen âVon unten nach obenâ)
inst.antworten = { [ziel.id]: "eingetragener Wert" } // Zielart âantwortâ (Eingabe in der Mappe)
Arbeitsziele (C4, spiel/arbeitsziele.js): Netzziele prĂŒft die Simulation am Netz; Arbeitsziele prĂŒfen, was der Spieler im Auftrag getan hat. Sim.pruefeZiel kennt sie nicht â nur Spiel.zielPruefen(netz, ziel, inst).
Ziel {typ:"befehl", geraet, muster:"^ipconfig( /all)?$", beispiel:"ipconfig", text}
// erfĂŒllt, wenn ein passender Befehl (Regex, ohne GroĂ/klein) im Auftrag auf dem GerĂ€t fehlerfrei lief
Ziel {typ:"antwort", id:"ip", frage:"Welche IP-Adresse hat die Kasse?", pruefen:{art:"ip"|"maske"|"gw"|"dns"|"mac", geraet, port?:"eth0"}, text}
// Soll kommt immer aus dem aktuellen Netz (Sim.adresse bzw. hw.macs) â nie aus dem Ticket; MAC in jeder Schreibweise
Spiel.istArbeitsziel(ziel) · Spiel.arbeitsziel(inst, ziel, netz?) â {ok, grund:null|"BEFEHL_FEHLT"|"ANTWORT_FEHLT"|"ANTWORT_FALSCH", text}
Spiel.antwortSoll(netz, ziel) â "192.168.1.10" | null · Spiel.antwortGleich(art, a, b) · Spiel.antwortSetzen(inst, ziel, wert) â PrĂŒfergebnis (Bus âantwortâ)
Spiel.arbeitszieleErfuellen(inst) // Durchspiel-Test: Befehle aus ziel.beispiel im Terminal ausfĂŒhren, Antworten aus dem Netz eintragen
Lösungsschritt {geraet, terminal:"netsh interface ip set address âŠ", text} // lĂ€uft in einer Terminal-Sitzung (Windows/Linux) auf dem GerĂ€t
ticket.art = ⊠| "terminal" // Terminal-Auftrag: Arbeitsziele + ggf. ein Netzziel; ohne Fehler im Netz erlaubt (nur Arbeitsziele)
- Windows (cmd): ipconfig (/all /release /renew /flushdns /displaydns) · ping (-n -l -t) · tracert · pathping · arp -a/-d · nslookup (auch interaktiv) · getmac · route print/add/delete · netstat -an · curl · telnet · netsh interface ip show config / set address ⊠static|dhcp / set dns · hostname · whoami · systeminfo · type âŠ\hosts · powershell (Test-NetConnection, Resolve-DnsName, Get-NetIPConfiguration).
- Linux (bash): ip a/r/link · sudo ip addr add|flush · sudo ip route add|replace|del default · sudo ip link set eth0 up|down · ping -c · traceroute · dig · nslookup · host · ss -tulpn · curl · cat /etc/resolv.conf|hosts|hostname · systemctl status|start|stop|restart|is-active (apache2, named, isc-dhcp-server, smbd, ssh, cups) · sudo tcpdump -n · hostnamectl · whoami.
- Ăndern geht nur ĂŒber netsh/route bzw. sudo ip/systemctl â immer ĂŒber den Verlauf (RĂŒckgĂ€ngig). Simulation: Ist der eingetragene DNS-Server das GerĂ€t selbst, antwortet der eigene Dienst lokal (Loopback) statt per ARP ins Leere.
- Terminal im Dock (
ui/terminal.js, PrĂ€fixtm-): Reiter âTerminalâ mit einer Sitzung je GerĂ€t (Reiterchen oben), Doppelklick auf ein GerĂ€t oder TasteTöffnet sie; âPakete ansehen âžâ nach Befehlen mit Aufzeichnung; Diagnosebefehle legen Beweiskarten in die Akte (Busbefehl).
9.6 Welle 3, E1 âFormen, Wahl, Ereignisse, Provisoriumâ
Auftragsformen (spiel/formen.js): Spiel.FORMEN[form] = {titel, sym, text} fĂŒr stoerung, projekt, terminal, forensik (Fernwartung: Netz unsichtbar, nur das Terminal eines Rechners), audit (Kundenplan prĂŒfen), beratung (Adressplan rechnen), hotline (RĂŒckfragen am Telefon, dann Störung). Spiel.formVon(def) â def.form || def.art (wartung â stoerung). Generierte Formen:
inst.gen = {skill, seed, opts} | {form:"forensik"|"audit"|"beratung", seed, opts:{stufe, kunde}}
Spiel.generiereForm(form, seed, opts) â def // deterministisch, id "form-<form>-<kunde>-<stufe|auto>-<seed>", registriert in Spiel.generierte
Spiel.risikoVon(def) â {stufe:1..3, text:"gering"|"mittel"|"hoch"} // aus dem Niveau: AbzĂŒge je Versuch (E 0, AP1 œ, AP2 1 â
)
Neue Arbeitsziele (wie befehl/antwort, geprĂŒft mit inst):
Ziel {typ:"tabelle", id, basis:"192.168.20.0/24", zeilen:[{name, hosts}], spalten:["netz","praefix","erster","letzter","broadcast"], text}
inst.tabelle = { ["zeile.spalte"]: "Eingabe" } // Soll aus den IP-Helfern (Spiel.beratung.soll(ziel)), Vergleich ohne Leerzeichen; PrĂ€fix â/26â oder â26â
Ziel {typ:"audit", fehler:["id.feld"], text} // die 1â3 Abweichungen im Kundenplan (def.kundenplan)
inst.audit = ["id.feld", âŠ] // markierte Zellen; erfĂŒllt = alle gefunden, nichts Falsches markiert
def.kundenplan = Plan (wie Spiel.plan.aus) mit 1â3 geĂ€nderten Tabellenwerten
Ziel {typ:"notiz", min:20, text} // Ănderungsnotiz fĂŒr den Kunden (Variante âsauberâ)
inst.notiz = "âŠ"
def.hotline = { anruf:"âŠ", max:3, fragen:[{id, text, antwort, wert:"gut"|"neutral"|"schlecht", warum}] }
inst.hotline = { gefragt:[id], fertig:bool } // Bewertung: gute Fragen â Ruf; Antworten als Karten in der Akte (art "hotline")
Postfach als Wahl (spiel/mischer.js): nach der Einstiegs-Wahl 3 Angebote (ab Stufe 2: 4), möglichst verschiedene Formen und Kunden. Je Form ist höchstens ein Angebot neu im Postfach; die Story-Reihenfolge der Handtickets bleibt je Form erhalten.
Spiel.mischer.waehlen({kandidaten:[{form, kunde, gewicht, schluessel}], offen:[{form, kunde}], verlauf:[form], n, z}) â [kandidat] // rein, ohne Ticketbau
Spiel.mischer.gesperrt(verlauf) â form|null // die Form der letzten zwei AbschlĂŒsse, wenn gleich
Spiel.postfach() // gesperrte Form steht unten â der Hub schlĂ€gt sie nie als nĂ€chstes vor
st.erledigt[i].form // Form jedes Abschlusses (Mischer, Tagebuch)
Provisorium oder sauber? (spiel/varianten.js): Handtickets mit Ănderungen an IOS-GerĂ€ten (Liste Spiel.VARIANTEN_TICKETS) bieten beim ersten Ăffnen eine Wahl. Provisorium: nur die Netzziele, kein Neustart-Test, Lohn Ă 0,6, Schuld â nach 3 weiteren AbschlĂŒssen kommt der Folgeauftrag (âDas Provisorium von neulich âŠâ: dasselbe Netz, der Fehler ist nach einem Neustart zurĂŒck, Ziele + Sicherung). Sauber: Netzziele + âgespeichertâ je geĂ€ndertem IOS-GerĂ€t + Ănderungsnotiz (notiz), Lohn Ă 1,2.
inst.variante = null|"provisorium"|"sauber" // ohne Wahl gelten die Grundziele
Spiel.varianten.fuer(def) â [{id, titel, text, lohnFaktor}] | null
Spiel.varianten.ziele(inst) â Ziele der gewĂ€hlten Variante · Spiel.varianten.waehlen(inst, id)
st.schulden = [ {id, ticket, kunde, seit:erledigtAnzahl, faellig:erledigtAnzahl+3, folge:iid|null, erledigt:bool} ]
Folgeauftrag-id "<ticket>-folge" (Spiel.varianten.folgeDef(def))
Ereignisse (spiel/ereignisse.js, Einstellung st.einst.ereignisse = "aus"|"selten"|"normal", Standard âseltenâ): höchstens eines je 15 (normal) bzw. 30 (selten) Minuten aktiver Zeit; deterministisch aus Spielstand-Seed und ZĂ€hler; jedes mit ErklĂ€rsatz; nie Fortschrittsverlust (Stufe, AuftrĂ€ge, Kunden bleiben; NetzĂ€nderungen laufen ĂŒber den Verlauf).
Spiel.EREIGNISSE[id] = {titel, sym, bedingung(ctx) â bool, ausloesen(ctx) â {text, warum, aktion?}}
// stromausfall (offener Auftrag mit IOS-GerĂ€t: alle IOS-GerĂ€te neu starten â nur Gespeichertes bleibt), kabelschaden,
// provider (Internet beim Kunden aus, bis âProvider anrufenâ), praktikant (zweiter kleiner Fehler), weiterempfehlung (neuer Kunde
// bietet einen Auftrag an), notfall (Notfall-Auftrag mit Frist â du wĂ€hlst die Reihenfolge)
st.ereignisse = { aktivMs, letzteMs, n, liste:[{t, id, kunde, text}] }
Spiel.ereignisse.tick(ms) â Ereignis|null · .ausloesen(id, ctx) (Tests) · .aktivZaehlen(ms)
9.7 Welle 3, E2 âKundenakte und Kompetenzkarteâ
Kundenakte (spiel/kundenakte.js, OberflĂ€che in der Ansicht âKundenâ, PrĂ€fix ka-):
st.kundenakte = { [kunde]: { punkte:0, atlas:[geraeteId], kapitel:0..3, gelesen:[nr], antworten:{[nr]: index} } }
Spiel.kundenakte.daten(kunde) · .vertrauen(kunde) â 1..5 // aus Punkten: â
â„ 4,5 +1 · âsauberâ +1 · Notfall rechtzeitig +1 · Provisorium 0
Spiel.kundenakte.atlasNetz(kunde) â Netz // das dokumentierte Netz des Kunden (Vorlage mit festem Seed) fĂŒr den Plan-Erzeuger
Spiel.kundenakte.atlas(kunde) â {plan, hell:[id], gesamt, komplett} // GerĂ€te leuchten, an denen man gearbeitet hat (geĂ€ndert oder geprĂŒft)
Spiel.kundenakte.nachAbschluss(inst, def, abnahme, {geaendert, variante}) â {neuHell:[id], vertrauen:{vorher, nachher}, kapitel:nr|null}
Spiel.kundenakte.kapitel(kunde) â [{nr, titel, frei:bool, ab:vertrauen, gelesen}] · .lesen(kunde, nr) · .antworten(kunde, nr, i) â {richtig, erklaerung}
Spiel.kundenakte.baustellen(kunde) â offene Schulden (Provisorium, Folgeauftrag noch nicht erledigt)
DATEN.geschichten[kunde] = [ {nr:1..3, ab:2..4, titel, text, senior, frage:{text, optionen, richtig, erklaerung}, quelle} ]
Kapitel 1 öffnet bei Vertrauen 2, Kapitel 2 bei 3, Kapitel 3 bei 4. Jedes Kapitel: kurze Szene beim Kunden mit kleiner Wendung, ein Satz des Seniors (Haltung: Gegenfrage statt Lösung, trocken-warm), eine Frage mit ErklĂ€rung, Quelle fĂŒr die fachliche Aussage.
Kompetenzkarte (spiel/kompetenz.js, im Lernstand statt der Fertigkeiten-Liste, PrÀfix kk-):
Spiel.KOMPETENZ_REGIONEN = [ {id, titel, skills:[idâŠ]} ] // 6 Regionen, zusammen genau die 27 Fertigkeiten aus DATEN.skills
Spiel.kompetenz.karte() â { regionen:[{id, titel, felder:[{id, name, zustand, box, nachbarn:[id]}]}], sichtbar, gesamt:27 }
// zustand: "nebel" (weder geĂŒbt noch neben einem geĂŒbten Feld noch freigeschaltet) | "offen" | Lernmotor-Stufe (âangefangenâ ⊠âgemeistert â
â)
9.8 Auftrag P âPolitur und Bindungâ (Design â SpielspaĂ 2.0, § 20/§ 21)
Erste Stunde (spiel/erstestunde.js, § 20 F3): Ein neuer Spielstand bekommt statt Zufall eine gestaltete Folge der ersten sechs AuftrĂ€ge; danach fĂŒllt der Mischer.
Spiel.ERSTE_STUNDE = [ {id:"kabel", ticket:"salon-01"}, {id:"stoerung", ticket:"salon-02"}, {id:"hotline", ticket:"salon-hotline", klingelt:true},
{id:"fernwartung", form:"forensik", opts:{kunde:"salon"}}, {id:"variante", ticket:"salon-05"},
{id:"empfehlung", form:"audit", ereignis:"weiterempfehlung", opts:{kunde:"baeckerei"}} ]
st.ersteStunde = { fertig:bool, ereignis:bool } // nur ein NEUER Stand beginnt mit fertig:false (Spiel.ergaenzer.ersteStunde);
// fehlt das Feld (alter Stand, Tests mit leererStand): vorbei
inst.kuratiert = 1..6 · inst.kuratiertId = "hotline" ⊠· inst.klingelt = true // Postfach-Reihenfolge, Anruf
Spiel.ersteStunde.aktiv() · .auffuellen() â [inst] · .ruhig() · .abholen() â Karte|null · .stand() â {aktiv, geschafft, gesamt, naechster}
ergebnis.ereignis = {id:"anruf"|"weiterempfehlung", sym, titel, text, warum?, aktion:"abheben"|"neu", iid} // Spiel.abschliessen
Spiel.ereignisse.ausloesen(id, {inst?, form?, kunde?, stufe?}) // ohne inst gilt der offene Auftrag; form/kunde/stufe sind WĂŒnsche
Spiel.postfachAuffuellenlegt wĂ€hrend der ersten Stunde die nĂ€chsten zwei offenen Schritte bereit (die Reihenfolge bleibt eine Wahl) und ruft weder Mischer noch Geschichtsreihe. Die Weiterempfehlung löst aus, sobald sie einer der nĂ€chsten zwei Schritte ist (nach dem vierten Abschluss) und bringt den Plan-Audit; zufĂ€llige Ereignisse ruhen bis dahin (ruhig()). Fehlen die AuftrĂ€ge der Folge (andere Daten, Testtickets), gilt die erste Stunde als vorbei.Spiel.postfach()sortiert nach Frist, dannkuratiert, dann wie bisher. Der Hub zeigt einen klingelnden Anruf als Hauptkarte (âMira Kaya ruft anâ, âRangehen âžâ).- OberflĂ€che:
UI.ereignisse.vormerken(e)zeigt die Karte, sobald kein Dialog und keine Funktionsprobe mehr lĂ€uft (Anruf mit grĂŒnem âRangehen âžâ und Klangklingeln, Weiterempfehlung mit âAnsehen âžâ âUI.spiel.imPostfach(iid)); Postfach-Karte mit Marke ââ klingeltâ.
Probe je Form (spiel/szene.js + ui/szene.js, § 20 F4): eigene, höchstens rund 3 s lange Funktionsprobe fĂŒr die neuen Formen, ĂŒberspringbar wie die Paket-Szene.
Spiel.szeneForm(inst) â null (Störung, Projekt, Terminal: Paket-Szene genĂŒgt)
| {art:"hotline", person, symbol, farbe, satz, minuten} // Hörer wird aufgelegt, Dank, GesprÀchsdauer (statt Sprechblase)
| {art:"fernwartung", geraet, name} // Sitzungsfenster: â auf dem Bildschirm, âVerbindung getrenntâ, âonline ââ â dann das Netz
| {art:"audit", kunde, korrigiert, tag} // Stempel âGEPRĂFT ââ mit Datum auf der Skizze des Netzes
| {art:"adressplan", basis, bereiche:[{name, netz, praefix, links, breite}]} // Bereiche leuchten nacheinander im Adressraum (Prozent)
UI.szene.abspielen(zeilen, {âŠ, form, aufdecken}) // Hotline/Fernwartung: höchstens 1 Paketweg; Audit/Adressplan: keine Paketfahrt
UI.labor.fernFertig() // Sitzungsfenster in den Zustand âgetrenntâ; aufdecken() hebt die Fernwartung auf (auch beim Ăberspringen)
Vertrauen zahlt aus (spiel/kundenakte.js, § 20 F5 â R4 âjede Belohnung öffnet etwasâ):
Spiel.VERTRAUEN_LOHN = {empfehlung:3, rabatt:4, rabattAnteil:0.2}
st.empfehlungen = { [kunde]: {von, tag} } // durch Empfehlung eine Stufe frĂŒher offen
st.kundenakte[kunde].empfohlen = true // dieser Kunde hat seine (einmalige) Empfehlung ausgesprochen
10 · DHCP-Tiefe (D1 aus Plan â Ausbau 1.2.md) â verbindlicher Vertrag (05.10.2026, Stand 2.9.0)
Wiederhergestellt am 31.10.2026 fĂŒr 3.0.0 (Commit 9716b08 hatte § 10â§ 18 gelöscht). Datenformen zuerst hier, dann im Code; Zahlen ausschlieĂlich aus docs/STAND.md.
Regressionsschutz. Nach jeder Ănderung an src/sim/ oder src/modell/: node tools/sim-stand.js â âSimulation unverĂ€ndert" oder die Abweichung ist ausdrĂŒcklich als gewollt benannt (mit BegrĂŒndung). Die Testzahl misst docs/STAND.md, nicht dieser Abschnitt.
10.1 Konfiguration (geraet.running, wandert in den Spielstand)
/* Router/Firewall als DHCP-Server: running.dhcp */
dhcp = {
an: false, // Server an/aus
pools: [{
name, netz, maske, gw, dns, // vorhanden
start: "", anzahl: 50, // Adressbereich wie host.js
domain: "", // Option 15
leaseS: 86400, // Lease-Dauer in Sekunden; 0/fehlend = Sim.T.LEASE
reservierungen: [{ mac:"02:00:âŠ", ip:"192.168.10.20", name:"drucker" }],
}],
ausgeschlossen: [{von, bis}],
}
/* Host (pc/server/nas) als DHCP-Server: running.dienste.dhcp â dieselben Pool-Felder
(start, anzahl, domain, leaseS, reservierungen); ausgeschlossen gilt auch hier. */
/* Switch: running.snooping */
snooping = { an:false, vertraut:["Gi0/1"] } // vertraute Ports (Richtung Server)
Einheitlichkeit ist Pflicht: Router-Pool und Host-Pool haben dieselbe Form; fehlende Felder werden defensiv gelesen (Standard), damit alte SpielstÀnde laden.
10.2 Laufzeit (netz.zustand[id])
leases: { [ip]: { mac, bis, t1, t2, hostname, abgelaufen:false } }
dhcp: { [port]: { ip, maske, gw, dns, server, bis, t1, t2, domain } } // Client-Sicht
snooping: { [port]: { verworfen:0 } }
t1 = bis - leaseDauer/2 (Erneuerung bei 50 %), t2 = bis - leaseDauer/8 (Rebind, nur Anzeige). Alte Leases ohne t1/t2 gelten als âkein T1 bekannt", nicht als Fehler.
10.3 Verhalten (Simulation)
- Ablauf in virtueller Zeit. Ist
L.t > l.bis, gilt die Lease als abgelaufen:Sim.adresseliefertquelle:"keine", der nĂ€chste Zugriff holt neu. Kein neuer Timer, kein neues Ereignis â Ablauf wird beim Zugriff festgestellt. - Erneuerung bei 50 %. GĂŒltige Lease und
L.t >= t1: Renew (Unicast Request/Ack); sonst Discover. - Reservierung. Passt die Client-MAC, wird diese IP angeboten (auch auĂerhalb
start..anzahl), aber nur wenn frei; sonst kein Angebot, GrundDHCP_RESERVED_BUSY. - Optionen. Der Ack trÀgt
domain(Option 15);leaseSkommt aus dem Pool. - Adresskonflikt. Vor dem Angebot prĂŒft der Server gegen GerĂ€te und fremde Leases; doppelt belegt â nĂ€chste Adresse, Konflikt wird vermerkt (
leases[ip].konfliktmit Zeitpunkt).show ip dhcp conflictgibt echte Zeilen aus. - Rogue-DHCP. Ein zweiter Server im LAN ist erlaubt; der Client nimmt das erste Angebot und kann falsches Gateway/DNS erhalten. Grund
DHCP_ROGUE_OFFER(Schicht 7). - DHCP-Snooping. Bei
snooping.anwerden Server-Nachrichten (UDP 67 â 68) von nicht vertrauten Ports verworfen; ZĂ€hler steigt, Client bekommt kein Angebot. GrundDHCP_SNOOPING_BLOCKED(Schicht 2). Client-Nachrichten (68 â 67) sind immer erlaubt.
10.4 Neue GrĂŒnde (Codes) â genau diese fĂŒnf, keine weiteren
| Code | Schicht | Ausgelöst wenn |
|---|---|---|
DHCP_LEASE_EXPIRED | 7 | Lease abgelaufen und in diesem Lauf kein neues Angebot |
DHCP_RESERVED_BUSY | 7 | Reservierung zeigt auf eine nicht freie Adresse â der Server gibt kein Angebot |
DHCP_ROGUE_OFFER | 7 | Client nimmt ein Angebot eines unerwarteten Servers |
DHCP_SNOOPING_BLOCKED | 2 | Server-Antwort an einem nicht vertrauten Switch-Port |
DHCP_CONFLICT | 7 | Eine Adresse liegt auf mehr als einem GerĂ€t; der Server ĂŒberspringt sie und nennt diesen Grund |
Vorrang, wenn mehrere zutreffen: DHCP_RESERVED_BUSY â DHCP_CONFLICT â DHCP_POOL_EMPTY â DHCP_LEASE_EXPIRED â DHCP_NO_OFFER. Der Client wiederholt den Discover bis zu 3Ă; ein Server-Grund steht einmal je Versuch in der Trace, das Ergebnis grund des Aufrufs ist genau einer. Jeder Code braucht Eintrag in Sim.GRUENDE, Lehrtext in drei Tiefen mit Quelle, Injektor, mindestens ein Ticket je Stufe und Aufnahme in die Grundcode-Liste der Tests.
10.5 Trace â unverĂ€ndert im Format
Keine neue art, kein neues Pflichtfeld im Ereignisobjekt (das optionale Warnfeld warnung aus § 5.2 bleibt die einzige ErgĂ€nzung). Renew nutzt die vorhandenen Typen request/ack; neue app.felder sind erlaubt (PDU-Ansicht). tests/sim-gruende.test.js prĂŒft die SchlĂŒsselmenge eines Ereignisses exakt.
10.6 OberflÀche und Konsole
- Lease-Tabelle im Inspektor (Router- und Server-Reiter): GerÀt · IP · MAC · Restlaufzeit · Zustand. Quelle ist
Sim.leases(netz, id). - Pool-Formular um
start,anzahl,domain,leaseS,reservierungenerweitern (Router und Host). - Switch-Reiter: Schalter âDHCP-Snooping" und die vertrauten Ports.
show ip dhcp binding|pool|conflictgilt auch auf Server-Hosts;bindingzeigt Restzeit/Zustand,conflictechte Konflikte,poolrechnetstart/anzahlund AusschlĂŒsse korrekt.- Windows
ipconfig /all: Lease erhalten/erstellt/ablauf aust1/t2/bis, plus DHCP-Server. Linux:dhclient/ip amit echter Restlaufzeit. - IOS-Poolmodus:
lease,domain-name,network,default-router,dns-server,host/hardware-address(Reservierung).
12 · Klassenraum (Lehrer/SchĂŒler, Hobby-Ebene) â Vertrag
Stand 3.0.0 (31.10.2026/01.11.2026). Dieses Kapitel ersetzt den am 30.10.2026 mit Commit 9716b08 gelöschten Abschnitt § 12 und fĂŒhrt ihn auf den gemessenen Code-Stand fort. Quellen: docs/entwicklung/Klassenraum/ AâD, historischer Stand 32cf209, src/spiel/klassenraum.js, src/spiel/klassenraum-codec.js, src/ui/klassenraum.js, src/spiel/abnahme.js, tests/klassenraum*.test.js, tests/ui-klassenraum*.test.js.
Kern in drei SĂ€tzen. Die Lehrkraft sagt einen kurzen Auftragscode an; jedes GerĂ€t baut daraus denselben Auftrag, weil AuftrĂ€ge deterministisch aus einem Seed entstehen. Es gibt keine Bewertung: der Ergebnis-Code trĂ€gt Sterne, Fehlversuche und Dauer fĂŒr den Lernstand des Einzelnen, keine Note, keine Rangfolge, keinen Vergleich. Ohne Konto, ohne Server, ohne Netz â Stufe A (Codec und Determinismus) und B (OberflĂ€che, Ampel, Export/Import) tragen den Unterricht vollstĂ€ndig; C (lokaler Server) und D (QR) sind nicht gebaut und bleiben 3.1 (§ 12.6/§ 12.7).
| Gegenstand | Festlegung |
|---|---|
| Auftragscode | NL-XXXX-XX â 10 Zeichen, 30 Bit Nutzteil + 2 PrĂŒfzeichen (mod 31 / mod 32), Alphabet ohne I, O, 0, 1 |
| Ergebnis-Code | E-XXXX-XXX â 9 Zeichen, 25 Bit (Sitzung 5 · Platz 5 · halbe Sterne 4 · Fehlversuche 2 · Dauer 9) + 2 PrĂŒfzeichen |
| Hilfecode | H-XXXX-XX â 9 Zeichen, 20 Bit (Sitzung 5 · Platz 5 · erfĂŒllte Ziele 5 · offene Ziele 5) + 2 PrĂŒfzeichen (§ 12.5) |
| Speicher | eigener SchlĂŒssel klassenraum neben labor, einst, sandbox, labor-sicherung; fassung: 2 seit 3.0.0 (§ 12.1) |
| Ăffnungsweg | Spiel.klassenraum.ausCode(code) â Spiel.instanzErstellen({âŠ, quelle:"klassenraum", ohneFlow:true}) â inst.klassenraum = {sitzung, platz, code} â Spiel.oeffnen(iid) |
| Abnahme | Spiel.klassenraum.abnehmen(inst) (delegiert an Spiel.abnahme), Ergebnis-Code nur bei bestanden; die Instanz wird danach entfernt, Geld/Ruf/Karriere bleiben unberĂŒhrt (src/spiel/abnahme.js) |
| Bewertung | keine â ausdrĂŒcklich Nutzerentscheidung 09.10.2026; wer spĂ€ter bewerten will, Ă€ndert einen Vertrag, nicht eine Einstellung |
12.1 Datenform â store "klassenraum", fassung: 2
Die Sitzung liegt nicht im Spielstand (store "labor"), sondern unter einem eigenen SchlĂŒssel: ein Schulrechner braucht sie unabhĂ€ngig vom Spieler-Fortschritt. Geschrieben wird entprellt (1500 ms, src/kern/basis.js); erzeugen und ergebnisEintragen rufen zusĂ€tzlich store.sofort().
/* store "klassenraum" â Wurzel. Ab 3.0.0: mehrere Sitzungen, eine aktive (KLR-01a). */
Speicher = {
fassung: 2, // Schemaversion dieses Teils; 1 = Altstand (eine sitzung)
programm: "<LABOR_VERSION>",
platz: null, // eigener Platz dieses GerÀts, 0..31, null = keiner
zuletzt: 1759706400000, // ms, jetzt()
sitzungen: [Sitzung, âŠ], // alle Sitzungen dieses GerĂ€ts, Reihenfolge = Anlage
aktiv: 7, // id der aktiven Sitzung | null
letzte: null, // zuletzt gelesener Code/Ergebnis (Anzeige)
zaehler: 3 // monoton, speist Sitzungskennung und Wunsch-Variante
}
/* Jede Sitzung hat genau diese 16 Felder (3.0.0: diagnose neu): */
Sitzung = {
id: 7, // Sitzungskennung 1..31 (5 Bit im Auftragscode)
titel: "Klassenraum-Auftrag",
art: "hand", // "hand" | "generiert"
ticketId: "salon-01", // genau eines von ticketId | (skill + seed)
skill: null, // Fertigkeit aus DATEN.skills, wenn generiert
index: 0, // Index in TABELLE_AUFTRAEGE bzw. TABELLE_SKILLS
variante: 42, // 8 Bit; seed = variante + 1
seed: 43, // 1..256
eigene: true, // Kanonisierung: eigene Fassung ja/nein
schritte: 5, // Lösungsschritte aus der Kanonisierung (additives Feld)
code: "NL-BAAA-BB", // gedruckter Auftragscode
dauerMin: 10, // geschÀtzte Dauer in Minuten
plaetze: 20, // KlassengröĂe, 0 = nicht eingestellt, sonst 1..31
erstellt: 1759706400000, // ms, jetzt()
ergebnisse: {}, // Abbildung platz â {platz, sterne, dauerS, versuche, code, zeit, quelle}
diagnose: {} // 16. Feld (3.0.0, KLR-05a): {[schritt]: anzahl} â anonyme H-Code-ZĂ€hler je Schritt; fehlt = {}
}
Migration fassung: 1 â 2 (3.0.0, verbindlich). Ein Altstand mit sitzung: Sitzung | null wird beim Lesen verlustfrei ĂŒbernommen: sitzungen = sitzung ? [sitzung] : [], aktiv = sitzung ? sitzung.id : null; alle ĂŒbrigen Felder bleiben. Ein fehlendes oder unbekanntes fassung wird wie 1 behandelt. Ein zu neuer Stand (fassung > 2) wird abgewiesen, nicht umgeschrieben.
Export/Import als Datei: {"format":"netzwerk-labor/klassenraum","fassung":2,"programm":âŠ,"zeit":âŠ, "sitzungen":[Sitzung,âŠ],"aktiv":id}. Der Import nimmt auch fassung: 1 ({âŠ,"sitzung":âŠ}) an und migriert; unbekannte Felder bleiben erhalten (verlustfrei, Object.assign-Verhalten aus 2.9.0). Beide Richtungen werfen nie â fremdes/beschĂ€digtes JSON ergibt {fehler:"format", grund}.
12.2 API â Spiel.klassenraum
18 Namen (Stand 2.9.0, gemessen an der RĂŒckgabe von src/spiel/klassenraum.js:448-450; 3.0.0 kommt auf 22 öffentliche Namen): erzeugen · ausCode · sitzung · ergebnisCode · ergebnisLesen · ergebnisEintragen · hilfeCode · hilfeLesen · exportieren · importieren · netzkennwert · tauglicheFertigkeiten · platz · platzSetzen · plaetze · plaetzeSetzen · abnehmen · tabellen.
| Aufruf | RĂŒckgabe | Zusicherung | ||
|---|---|---|---|---|
erzeugen({ticketId?, skill?, seed?, dauerMin?, titel?, plaetze?, bestaetigen?}) | sitzung \ | {warnung, sitzung?} | legt eine Sitzung an und schreibt sofort; fehlender Seed wird gezogen und steht im Code; hat die aktive Sitzung bereits Ergebnisse, wird sie ohne bestaetigen: true nicht ersetzt â RĂŒckgabe {warnung:"sitzung-hat-ergebnisse"} (3.0.0) | |
ausCode(code) | {ticketId, seed, art, sitzung, index, variante, skill, eigene, schritte, âŠ} \ | {fehler, grund} \ | null | wirft nie; null nur bei leerer Eingabe; normalisiert GroĂ-/Kleinschreibung und Trennstriche |
sitzung() | aktive Sitzung \ | null | liest nur store "klassenraum", nie den Spielstand | |
sitzungen() | [{id, titel, art, code, erstellt, plaetze, ergebnisse, aktiv}] | neu 3.0.0 (KLR-01a): Ăbersicht aller Sitzungen, Kopien, keine Innenreferenzen | ||
sitzungWaehlen(id) | {ok, sitzung} \ | {fehler:"sitzung", grund} | neu 3.0.0: setzt aktiv; unbekannte id ist ein Fehler, kein stilles Ersetzen | |
ergebnisCode(inst, abnahme) | "E-âŠ" \ | {fehler, grund} | trĂ€gt Sitzung, Platz, halbe Sterne, Fehlversuche, Dauer und PrĂŒfsumme; nur bei bestandener Abnahme | |
ergebnisLesen(code) | {ok, sitzung, platz, sterne, dauerS, versuche, code} \ | {fehler, grund} | wirft nie; prĂŒft PrĂŒfsumme und Codewert | |
ergebnisEintragen(code) | {ok, neu, platz, sterne} \ | {fehler, grund} | idempotent (derselbe Code Àndert nichts, neu:false); fremde Sitzung ist ein Fehler | |
hilfeCode(inst) · hilfeLesen(code) | "H-âŠ" \ | {fehler, grund} · {ok, sitzung, platz, schritt, offen, code} | siehe § 12.5; wirft nie | |
exportieren() · importieren(text) | JSON-String · {ok, sitzungen, aktiv} \ | {fehler, grund} | verlustfrei, ĂŒberstehen Neustart; 3.0.0: alle Sitzungen (§ 12.1) | |
netzkennwert(netz) | 6 Zeichen \ | "" | siehe § 12.4; keine Ausnahme | |
tauglicheFertigkeiten() | [{skill, index, ersterSeed}] | nur Fertigkeiten, fĂŒr die hier wirklich ein Auftrag baubar ist | ||
platz() · platzSetzen(z) · plaetze() · plaetzeSetzen(z) | 0..31 · {ok} \ | {fehler:"wahl"} | eigener Platz · KlassengröĂe der aktiven Sitzung (0 = nicht eingestellt) | |
abnehmen(inst) | {ok, klassenraum, bestanden, sterne, abnahme, lernen, def, inst, âŠ} | Spiel erhĂ€lt kein Geld/keinen Ruf; lernen lĂ€uft ĂŒber den normalen Lernmotor | ||
tabellen() | {auftraege, skills, âŠ} | PrĂŒfflĂ€che; eingefrorene Tabellen (§ 12.8) |
3.0-Erweiterung KLR-04a (Sitzungen löschen/ersetzen, keine stille Kappung). Verbindlich:
| Aufruf | RĂŒckgabe | Verhalten | ||
|---|---|---|---|---|
erzeugen({âŠ, bestaetigen:true}) | sitzung \ | {warnung:"sitzung-hat-ergebnisse"} | ersetzt die aktive Sitzung mit Ergebnissen nur mit bestaetigen:true; die alte Sitzung bleibt in sitzungen[] erhalten (KLR-01a). 32. Sitzung (alle Kennungen 1..31 belegt) â {fehler:"sitzung", grund} ohne Ănderung. | |
sitzungLoeschen(id, {bestaetigen?}) | {ok:true, geloescht, aktiv} \ | {warnung:"sitzung-hat-ergebnisse"} \ | {fehler:"sitzung", grund} | neu 3.0.0 (KLR-04a): löscht eine gespeicherte Sitzung samt Ergebnissen. Unbekannte id â Fehler, kein stilles Nichts. Hat die Sitzung Ergebnisse, nur mit bestaetigen:true (sonst Warnung ohne Ănderung). War sie aktiv, wird aktiv auf die zuletzt angelegte verbleibende Sitzung gesetzt oder auf null; frei gewordene Kennung darf wieder vergeben werden; zaehler bleibt monoton. |
erzeugen({âŠ, plaetze}) | ⊠| kein stilles Klemmen mehr: plaetze > 31 oder negativ â {fehler:"wahl", grund} ohne Anlegen (vorher still 0); 0 bleibt ânicht eingestellt". |
Abnahme: tests/klassenraum-sitzungen.test.js (oder neue Datei) â 31 Sitzungen anlegen â 32. ergibt {fehler:"sitzung"} und Speicher unverĂ€ndert; sitzungLoeschen ohne bestaetigen bei Ergebnissen â Warnung ohne Ănderung; mit bestaetigen:true â Sitzung weg, aktiv sauber, freie Kennung wieder vergebbar; Gegenprobe rot bei eingeschmuggeltem Fehlverhalten.
Ergebnis-Korrektur (KLR-04a, 31.10.2026) â ergĂ€nzt die Tabelle:
| Aufruf | RĂŒckgabe | Verhalten | ||
|---|---|---|---|---|
ergebnisEintragen(code, {ersetzen?, bestaetigen?}) | {ok, neu, platz, sterne} \ | {ok:true, neu:false, ersetzt:true} \ | {warnung:"platz-hat-ergebnis"} | derselbe Code bleibt idempotent (neu:false, Grund âdoppelt"); ein anderer Code am selben Platz ersetzt nur mit ersetzen:true und bestaetigen:true, sonst {warnung:"platz-hat-ergebnis"} ohne Ănderung. |
ergebnisEntfernen(platz, {bestaetigen?}) | {ok:true, entfernt:platz} \ | {fehler:"platz", grund} \ | {fehler:"wahl", grund} | neu 3.0.0 (KLR-04a): entfernt den Eintrag am Platz aus der aktiven Sitzung; kein Eintrag dort â {fehler:"platz"} (kein stilles Nichts); Platz auĂerhalb 0..31 â {fehler:"wahl"}. Die RĂŒckfrage [Abbrechen][Ersetzen][Entfernen] fĂŒhrt die OberflĂ€che (task-41). |
Grenz-Signale: ergebnisEntfernen meldet unzulĂ€ssige PlĂ€tze als wahl; ergebnisEintragen behĂ€lt fĂŒr kaputte Codes die Vertragsklassen aus § 12.3 (lĂ€nge/zeichen/prĂŒfziffer), damit bestehende Tests und UI-Texte gĂŒltig bleiben. erzeugen({plaetze > 31}) â {fehler:"wahl"} (siehe voriger Absatz).
API-Umfang 3.0.0: öffentlich sind 25 Namen â die 18 oben plus sitzungen, sitzungWaehlen, sitzungLoeschen, ergebnisEntfernen. migrierenStand (SPL-10) ist interner Helfer und wird nur von Spiel.laden() aufgerufen, nicht von der OberflĂ€che; ihn fĂŒhren Doku/Test als âinternâ.
Diagnose-Aggregat (KLR-05a, 31.10.2026). Anonyme ZĂ€hler der gelesenen Hilfecodes je âSchritt" (= erfĂŒllte Ziele):
| Aufruf | RĂŒckgabe | Verhalten | |
|---|---|---|---|
diagnoseZaehlen(code) | {ok:true, schritt, anzahl} \ | {fehler, grund} | liest einen H-Code ĂŒber hilfeLesen und zĂ€hlt ihn in der Sitzung des Codes (auch wenn eine andere aktiv ist, analog ergebnisEintragen). Unbekannte Sitzung â {fehler:"sitzung"}; unlesbarer Code â bestehende Codec-Fehlerklassen. Wiederholtes Einlesen zĂ€hlt erneut (bewusste Grenze, kein Personenbezug, keine Dedup-Daten). |
diagnose() | {plaetze, gesamt, schritte:[{schritt, anzahl}]} | anonymes Ziel-Histogramm der aktiven Sitzung; plaetze ist der Nenner fĂŒr âSchritt k: n von 20â, schritte aufsteigend, nur beobachtete Schritte. Keine Einzelzuordnung, kein Personenbezug. |
Abnahme: tests/klassen-diagnose.test.js â 20 H-Codes (5ĂSchritt 1, 7ĂSchritt 2, 8ĂSchritt 3) â ZĂ€hlung 5/7/8, gesamt 20; Gegenprobe: ein eingeschmuggeltes Personenfeld (name/platz) macht rot; keine Einzelzuordnung im Speicher. Kein neues Bedienelement (R12 bleibt 6; Anzeige = KLR-05b).
20-Ăbersicht (KLR-08a, 31.10.2026) â ergĂ€nzt die Tabelle:
| Aufruf | RĂŒckgabe | Verhalten | |
|---|---|---|---|
uebersicht() | null \ | {plaetze, eingerichtet, dauerMin, erstellt, abgegeben:[platz], offen:[platz], fertig, offenAnzahl, ohnePlatz, restMinuten, abgelaufen} | neu 3.0.0 (KLR-08a): reine Daten der aktiven Sitzung, keine Namen. abgegeben = PlĂ€tze mit Ergebnis (aufsteigend); offen = PlĂ€tze 1..plaetze ohne Ergebnis (aufsteigend); fertig/offenAnzahl = LĂ€ngen; ohnePlatz = EintrĂ€ge mit Platz 0 oder > plaetze; restMinuten = max(0, ceil((erstellt + dauerMin·60000 â jetzt())/60000)), abgelaufen = restMinuten 0. plaetze:0 â eingerichtet:false, offen:[], offenAnzahl:0 (kein erfundener Nenner); ohne aktive Sitzung null. |
erzeugen({âŠ}) erbt die KlassenstĂ€rke: fehlt plaetze beim Anlegen, ĂŒbernimmt die neue Sitzung die KlassengröĂe der bisher aktiven Sitzung (sichtbar statt still 0); ein ausdrĂŒckliches plaetze (auch 0) gewinnt.
Abnahme: tests/klassen-uebersicht.test.js â plaetze 20, Ergebnisse auf 1/2/5, T+2 min bei dauerMin 10 â fertig 3, offenAnzahl 17, offen 3/4/6..20, restMinuten 8; GrenzfĂ€lle plaetze 0, alle 20 abgegeben, abgelaufen, keine Sitzung; KlassenstĂ€rke-Erbe getestet.
Wortwahl im Klassenraum (3.0.0, verbindlich): Die Bestands-Tafel der Ampel behĂ€lt âPlatz Nâ (Platz N · Sterne · Zeit â B-Vertrag/O1); alle neuen 3.0-FlĂ€chen nutzen âSitzplatz Nâ, und das Wort âAbgabeâ gilt einheitlich in neuen Flows (Sitzungsliste, Dialoge, Ăbersicht, Diagnose). Die 20-Ăbersicht und die Ampel nutzen eine Definition: âfertig/abgegebenâ zĂ€hlt nur gĂŒltige PlĂ€tze 1..plaetze, Ergebnisse mit ungĂŒltigem Platz werden separat als âAbgaben ohne gĂŒltigen Platzâ ausgewiesen (KLR-10).
Ăffnungsweg (verbindlich). Ein neuer Auftrag entsteht nur ĂŒber Spiel.instanzErstellen({ticketId | gen:{skill, seed}, seed, quelle:"klassenraum", ohneFlow:true}) â vorher prĂŒfen, denn der Aufruf wirft bei unbekanntem Ticket. ohneFlow: true ist Pflicht, sonst macht der lokale Flow-Regler denselben Code auf zwei GerĂ€ten verschieden. Eine Klassenraum-Instanz belegt keinen Postfach-Platz und ist im Postfach unsichtbar (Entscheidung E1).
12.3 Fehlerformen
Alle Fehler sind Daten, keine Ausnahmen: {fehler:<Klasse>, grund:<Satz>}. Verbindliche Klassen (Stand 2.9.0, gemessen im Code): lĂ€nge · zeichen · prĂŒfziffer · auftrag · fassung · sitzung · wahl · format · abnahme · bereich. null nur aus ausCode/ergebnisLesen, wenn gar nichts eingegeben wurde. Der Satz ist Anzeigetext und gehört der OberflĂ€che; wörtlich vorgegeben ist nur âPrĂŒfziffer stimmt nicht â hast du ein O statt 0 getippt?".
12.4 Der Netzkennwert (Messform)
Der Determinismus wird gemessen, nicht behauptet: Spiel.klassenraum.netzkennwert(netz) liefert 6 Zeichen aus dem Alphabet, FNV-1a-32 ĂŒber das kanonische Netz (geraete und kabel sortiert, netz.zustand bleibt drauĂen), höchstwertige 5 Bit zuerst. Gleicher Abdruck = derselbe Auftrag; ungleicher Abdruck = Alarm, dann ist der Seed nicht vollstĂ€ndig im Code gelandet. GeprĂŒft in tests/klassenraum-determinismus.test.js (zwei getrennte Prozesse, zeichengleich).
12.5 Hilfecode H-XXXX-XX
Der Azubi kann der Lehrkraft sagen, wo er hĂ€ngt, ohne Server, Konto oder Bewertung. Form: 9 Zeichen â H + Bindestrich + 4 Nutzzeichen + Bindestrich + 2 PrĂŒfzeichen; dasselbe 32-Zeichen-Alphabet und dieselben PrĂŒfzeichen wie oben. Nutzteil 20 Bit: sitzung 5 (1..31) · platz 5 (0..31) · schritt 5 (erfĂŒllte Ziele) · offen 5 (offene Ziele). UngĂŒltige Bitlage: sitzung = 0 (erzeugt der Encoder nie). Die Lehrkraft tippt den Code in ihr bestehendes Feld âErgebnis-Codes" (mehrere Codes passen hinein) â kein neues Bedienelement, R12 bleibt bei sechs sichtbaren Elementen (LeitungsÂentscheidung O1). Der Hilfecode ist kein Bewertungssignal; er beschreibt nur Ziele, die schon erfĂŒllt bzw. offen sind.
12.6 Lokaler Server (Stufe C) â 3.1, im Produkt nicht gebaut
Ehrlicher Stand 2.9.0: shell/src-tauri enthĂ€lt keinen Server, src/ enthĂ€lt keinen QR; der Schalter âKlassenraum-Server" in den Einstellungen hat deshalb keine Wirkung und wird in 3.0 entweder entfernt oder als ânicht in dieser Fassung" umtextet (VIS-04). Die Bauform der Stufe C steht fest und ist belegt: C1 in der Desktop-.exe (Rust), C2 als eigenstĂ€ndiges Binary in tools/klassenraum (eigenes Cargo-Projekt, cargo build --release, cargo test 10/10, pwsh -File tools\klassenraum-probe\C-http-probe.ps1 20/20 â selbst gemessen am 31.10.2026 laut docs/STAND.md/AGENTS.md). Beide Endpunkte, nur lokal, keine Klarnamen, Standard aus:
GET /liste?auftrag=<Auftragscode>â200+{"format":"klassenraum-liste","v":1,"sitzung":âŠ,"anzahl":âŠ,"sitzungen":[âŠ],"ergebnisse":[âŠ]}POST /ergebnisâ201(neu) /200(idempotent) +{"format":"klassenraum-ergebnis","v":1,"ok":âŠ,"neu":âŠ,"anzahl":âŠ,"platz":âŠ,"sitzung":âŠ,"lagerVoll":âŠ}- Fehler â
400/409/413/415+{"format":"klassenraum-fehler","v":1,"fehler":âŠ,"feld":âŠ}
Der Einbau von C1 ist nicht 3.0-Bestandteil: er braucht eine gemessene 2-Rechner-Umgebung (KLR-06 â 3.1). Ohne Server bleibt die Klasse arbeitsfĂ€hig (Codes eintippen).
12.7 QR-Code (Stufe D) â 3.1, im Produkt nicht gebaut
Inhalt wĂ€re genau der Auftragscode â kein Link, keine Adresse, kein Konto, damit jeder handelsĂŒbliche Scanner funktioniert; die Desktop-Fassung wĂŒrde das Bild in Rust erzeugen und als Data-URI an die OberflĂ€che reichen. In der Browser-Fassung bleibt Abtippen der Normalfall. Der Wiki-Text darf keinen gebauten QR behaupten (VIS-04); die UI folgt erst nach dem JS-Port (KLR-07 â 3.1).
12.8 Grenzen (bewusst) und 3.0/3.1-Schnitt
| Grenze | Festlegung | Wo belegt |
|---|---|---|
| Adressierbare AuftrĂ€ge | 58 von 76 Handtickets â der Auftragscode hat 6 Bit (0..63); die 2.1-Tickets sind nicht klassenraum-adressierbar, der Formatwechsel kommt erst 3.1 mit Migrationsweg | src/spiel/klassenraum.js TABELLE_AUFTRAEGE, docs/STAND.md Ausnahme âKlassenraum-Codec" |
| Fertigkeiten | 30 in DATEN.skills, davon 28 mit Injektor; lab.ipv6/lab.wlan stehen ehrlich auf tauglich:false (inhaltlich geĂŒbt, nicht paketweise simuliert) | TABELLE_SKILLS, § 17.1 |
| Sitzungen (3.0.0) | mehrere Sitzungen je GerÀt, eine aktiv; keine still ersetzte Sitzung mit Ergebnissen (KLR-01a/b); Sitzungskennung 1..31, PlÀtze 0..31 | dieser Abschnitt, § 12.1/§ 12.2 |
| Kein Schutz gegen Abschreiben | wer den Code hat, hat den Auftrag â gewollt | â |
| Keine IdentitĂ€t, keine Zeit-/Sperrlogik, keine Serverpflicht, kein QR im Browser, keine Auswertung ĂŒber die Ampel hinaus | 2.x/3.0 | D § 8 |
| 3.1 | C1-Server, QR-UI, Codec-Formatwechsel mit Migration, Diagnose bleibt anonym (§ KLR-05 in docs/entwicklung/Konzept â 3.0.md) | Konzept 3.0 §5 |
Abgrenzung zu Ă€lteren Dokumenten. docs/entwicklung/Klassenraum/ AâD bleiben Protokolle; wo sie sich widersprechen, gilt dieser Abschnitt (Leitungsentscheidungen L1âL7 und O1 vom 09.10.2026). Insbesondere: E1 (unsichtbar im Postfach), keine Bewertung, plaetze gehört zur Sitzung, R12 misst je Ansicht und wird fĂŒr die Klassenraum-Ansichten nicht aufgeweicht.
13 · Hilfestellung â Stufen und Schnittstellen (07.10.2026) â verbindlicher Vertrag
Wiederhergestellt 31.10.2026. VollstĂ€ndiger Vertragstext: entwicklung/Hilfestellung â Stufen und Schnittstellen.md; bei Widerspruch gilt der Vertragstext. Dieser Abschnitt hĂ€lt fest, was fĂŒr den ganzen Bau gilt.
Anlass (Auftrag des Nutzers, 07.10.2026). Ein Azubi kann die Syntaxen nicht kennen, hat im Spielflow aber keinen Zugriff auf eine Lernnotiz. Die Hilfe muss dorthin, wo er steht: ins Terminal, in die Leiste und in einen Trainingsbereich abseits der AuftrĂ€ge â schrittweise mit dem Bildungsstand wachsend.
13.1 Die drei Achsen (nicht verwechseln)
| Achse | Feld | Werte | Bedeutung | |||
|---|---|---|---|---|---|---|
| Bildungsstand | Spiel.einst.stufe | azubi \ | azubi-plus \ | geselle \ | meister | Voreinstellung des Menschen; wird nie automatisch geÀndert |
| ErklĂ€rtiefe | Spiel.einst.niveau | E \ | AP1 \ | AP2 | Wie ausfĂŒhrlich Konsole und Simulation erklĂ€ren | |
| PrĂŒfungsstrenge | Spiel.einst.wahl, inst.niveau | auto \ | E \ | AP1 \ | AP2 | Wie streng ein einzelnes Ticket bewertet wird |
Spiel.stufe.setzen(id) zieht einst.niveau nach, lÀsst es aber einzeln verstellbar.
13.2 Neue und geÀnderte Datenformen
| Gegenstand | Festlegung |
|---|---|
| Rang | Spiel.stufe.rang(id) â 1âŠ4; verglichen wird immer ĂŒber den Rang, nie ĂŒber den Namen; RĂŒckfall azubi (kein Wurf) |
| Hilfekonto je Ticket | Spiel.HILFE_KONTO = {azubi:6, "azubi-plus":4, geselle:2, meister:0}; gezĂ€hlt je Ticket ĂŒber Spiel.stufe.hilfeZiehen(inst); ein leerer Vorrat sperrt nicht â die nĂ€chste Sprosse kostet Sterne |
| VorschlĂ€ge | DATEN.hilfen.VORSCHLAEGE; bereich ist immer eine Spiel.LEITER[].id (link, vlan, ip, gateway, route, dienst); art â {pruefen, aendern} |
| Entscheidung | Spiel.hilfe.passend/geruest/leiter/syntaxBruecke (nur angefĂŒgt); die OberflĂ€che entscheidet nichts selbst |
| Fehlertexte | Spiel.fehlertext({netz, id, modus, art, eingabe, ausgabe, fehler}), Tiefe aus Spiel.stufe.text(...) |
| Mini-Anker | Spiel.mini.hilfe(id) (Denkhilfe, nie die Lösung) und Spiel.mini.anker(id) â {titel, text, quelle, wiki} |
| Training | Spiel.st.training = {je: {[szenarioId]: {versucht, bestanden, bestes}}} â Spielstand v:3; Szenarien DATEN.trainings, Logik Spiel.training |
13.3 Verhalten, das vertraglich feststeht
- Training zahlt nichts. Kein Geld, kein Ruf, kein Karrierefortschritt, kein Eintrag in
st.erledigt, keine Wochenwertung, kein Postfach; es zĂ€hlt nur fĂŒr den Lernmotor (L.ueben) und den Trainingsstand. Durchgesetzt an einer Stelle: erste Zeile vonSpiel.abschliessenleitetquelle === "training"um. quelle: "training"ist unsichtbar.Spiel.postfach()filtert sie heraus (wiepruefung/raetsel).- Die Hilfe ist schrittweise. Je Stufe 0/1/2 VorschlĂ€ge (
meister0), Denkhilfe bei jedem Mini (azubi/azubi-plus), nach falscher Antwort (geselle), gar nicht (meister). - Nichts wird ungefragt verraten.
Spiel.mini.hilfeliefert nie den Lösungstext; ein Test prĂŒft das gegenSpiel.mini.loesungText. - Kein Baustein ohne Aufruf. Jede FlĂ€che wird aus dem echten Weg gerufen (Terminal-Zeile, Leisten-Knopf, Ansicht) â grĂŒne Tests ohne Aufruf gelten als nicht fertig.
15 · Nachtrag des Nutzers: Wiederholungssperre, FĂŒhrung, generierte Fragen (09.10.2026) â Vertrag
Wiederhergestellt am 31.10.2026 fĂŒr 3.0.0.
Anlass. Nach dem Fahrplan-Auftrag verlangte der Nutzer vier Dinge: (1) AuftrĂ€ge dĂŒrfen sich in einer Sitzung nicht schnell wiederholen, (2) im Labor soll bei Bedarf der nĂ€chste nötige Schritt gezeigt werden (âpulsiert oder ein Pfeil darauf zeigt"), (3) Fragen sollen möglichst immer neu generiert werden, (4) alles bis zum Production Release ausarbeiten.
15.1 Neue und geÀnderte Datenformen
| Gegenstand | Festlegung |
|---|---|
| Wiederholungssperre | Spiel.st.mini trĂ€gt zug (Zugnummer) und gespielt (Mini-id â Zugnummer); zuletzt bleibt das Sperrfenster (25). Pro Fertigkeit kommt jedes Mini einmal, danach das am lĂ€ngsten nicht gespielte. Alte/beschĂ€digte StĂ€nde werden rekonstruiert, nie geworfen |
| Mischung | Spiel.MINI.GENERIERT = 3 â jede dritte Frage darf generiert sein |
| Generierte Fragen | DATEN.fragenVorlagen (11 Vorlagen) und DATEN.fragenDenkfehler (20 Arten); Spiel.fragen.erzeuge/erzeugeVoll/fuerSkill/merken/von/ausId/alle; Id gf-<vorlage>-<seed>, aus sich selbst wiederherstellbar; generierte Fragen stehen nicht in Spiel.mini.alle() |
| NĂ€chster Schritt | Spiel.naechster(inst) â {geraet, ziel, text, bereich} oder ehrlich null; dazu Spiel.naechsterBedarf/-Wartezeit/-Wann/-Stufe, FUEHRUNG_NACH_MS 90 s, FUEHRUNG_ZEIGEN_MS 15 s, NAECHSTER_WANN (azubi/azubi-plus ânachzeit", geselle ânachfehler", meister ânein") |
| Vierte Quelle | quelle: "klassenraum" ist aus Postfach, Offen-ZĂ€hler und âHeute"-Kachel ausgenommen (wie training) und nimmt am Ereignistakt nicht teil |
15.2 Verhalten, das vertraglich feststeht
- Rotation statt Filter. Jedes Mini einer Fertigkeit kommt einmal dran, bevor sich eines wiederholt; bei echter Erschöpfung das am lĂ€ngsten nicht gespielte. Alles deterministisch ĂŒber
Zufallâ keinMath.random, kein Datum. - Die FĂŒhrung verrĂ€t nichts. Der Statustext der Simulation ist die Lösung; bei Netz-Zielen zeigt der Fingerzeig deshalb nur GerĂ€tename und Zieltext, nie die Ursache. Er erscheint nur auf Bedarf (Knopfdruck oder 90 s ohne Fortschritt), einmal je Stillstand, und verschwindet, sobald der Schritt getan ist;
meisterbekommt sie nie ungefragt. - Generierte Fragen sind bewiesen richtig. Die Vorlage rechnet ihre Lösung selbst, der Generator lĂ€sst nur Fragen durch, bei denen genau eine Option besteht; der Test rechnet mit eigener Arithmetik nach und prĂŒft jede falsche Option gegen die dokumentierte Denkfehler-Menge.
- § 13.3.1 gilt auch ĂŒber den Ereignisweg. Der 20-Sekunden-Ereignistakt nimmt
trainingundklassenraumaus â vorher zahlten beide ĂŒberweiterempfehlungRuf und legten neue Tickets an. - Die Wiki zeigt nur, was das Spiel kann â oder kennzeichnet den Befehl ausdrĂŒcklich als echtes GerĂ€t (âEchtes IOS, nicht das Labor"). Ein WĂ€chter fĂŒhrt jede Rezeptzeile mit Prompt in der echten CLI aus.
16 · Welle 1 (2.1 + 3.0-GerĂŒst) â verbindlicher Vertrag (26.10.2026)
Wiederhergestellt am 31.10.2026 fĂŒr 3.0.0.
Anlass. Diese Session baut das Maximal-Programm 3.0 in Wellen. Welle 1 umfasst 2.1 S1/S2 (Wahrheit, Korrektheit) sowie parallel UX-P1 und das Werkstatt-GerĂŒst. Damit Code und Doku nicht auseinanderlaufen, steht hier zuerst die Datenform.
16.1 Eine Zahlenquelle: docs/STAND.md (generiert)
| Gegenstand | Festlegung |
|---|---|
| Erzeuger | tools/stand.py erzeugt docs/STAND.md und einen markierten Block in README.md; Quelle sind Messungen (Testlauf, Modulliste des Baus, Dateisystem ort), keine getippten Zahlen |
| Blockmarker | <!-- STAND:ANFANG --> ⊠<!-- STAND:ENDE -->; Handarbeit in diesem Bereich wird ĂŒberschrieben |
| PrĂŒfmodus | python tools/stand.py --pruefen vergleicht erzeugten Text mit den Dateien; Abweichung â Exit 1 mit betroffener Zeile |
| Pflichtfelder | Fassung, Baukennung, Tests (grĂŒn/gesamt/ĂŒbersprungen), Testdateien, headless Module, Bau-Module, src/-Dateien und -Zeilen (Methode benannt), Einzeldatei Bytes + SHA256, Inhalte (Tickets, Minis, Injektoren, Trainings, Wiki-Seiten, Fertigkeiten), docs/doku-Seiten |
| Geltung | README, Liesmich, AGENTS, Bauen, SITZUNGSABSCHLUSS und CHANGELOG nennen diese Zahlen nur noch ĂŒber STAND.md; historische Abschnitte bleiben unangetastet |
| R12-Messung | Rohmessung Nachweise/rauch/r12.json (rauch.py --r12, je Breite Ă Ansicht, gemessen:false + Grund statt 0); verbindliche Datei Nachweise/3.0/r12.json (ethos.py --dom --r12); eine Zahlenquelle ist die Zeile âR12 Bedienelementeâ in docs/STAND.md; Norm = gemessene Ratsche: Anzahl VerstöĂe ĂŒber der Grenze aus tests/stil-stand.json, Zuwachs = ROT |
16.2 Skip-Gate (2.1 S1)
tests/run.js endet mit Exit 1, wenn uebersprungen(ergebnisse) > 0. Ein Test ohne Zusicherung gilt damit als rot, nicht als grĂŒn. tools/test.sh reicht den Exit-Code unverĂ€ndert durch; die Bilanzzeile bleibt im Text. Die Definition âĂŒbersprungen = 0 Zusicherungen" (tests/harness.js) bleibt unverĂ€ndert.
16.3 TTL und Probe-Art je Betriebssystem (2.1 S2)
| Gegenstand | Festlegung |
|---|---|
| TTL-Startwerte | src/sim/l3.js: Windows-Ziele 128, Linux-Ziele (Server/NAS mit Linux-Betriebssystem) 64. Genau ein Startwert je EndgerĂ€tetyp (PC/Server/NAS); Router, Switch und Internet fĂŒhren ihre eigenen TTL-Konstanten in ihren Modulen |
| Traceroute | Windows-EndgerĂ€te senden ICMP-Echo; Linux-EndgerĂ€te senden UDP-Proben auf hohe Ports, wie im Wiki gelehrt. Die sichtbare Ausgabe bleibt gleich; nur Sim-GrĂŒnde dĂŒrfen abweichen |
| Referenzstand | tests/sim-stand.json wird nach der Ănderung bewusst neu erzeugt; die Abweichung wird im CHANGELOG begrĂŒndet. Danach ist node tools/sim-stand.js grĂŒn |
16.4 Design-Tokens und Bewegung (UX-P1)
| Gegenstand | Festlegung |
|---|---|
| Dauer-Tokens | --dauer-kurz:80ms · --dauer:160ms · --dauer-mittel:240ms · --dauer-lang:320ms; ZÀhler --dauer-zaehler:700ms; Easings --ease-standard, --ease-out, --ease-pop |
| JS-Zugriff | genau eine Funktion UI.zeit() liest Tokens per getComputedStyle; Animations-/Wartepfade in src/ui/ enthalten keine ms-Literale mehr. SCHRITT_MS bleibt Datenkonstante, wird aber an das Tempo gekoppelt |
| Lesbarkeit | alle Text-Tokens â„ 4,5:1 auf --bg/--panel/--panel-2 in beiden Schemata |
| Press | genau eine .knopf:active-Transform-Regel; Druck 80 ms |
| Icons | UI-Chrome (Knöpfe, Ăberschriften, Chips) nutzt UI.symbol; Emoji nur in Klartext-Copy auĂerhalb der Chrome |
16.5 Werkstatt-GerĂŒst (3.0, Welle 1)
Wichtig: Ein freies Labor existiert bereits (UI.sandbox in src/ui/start.js, Store "sandbox", v:1, Autosave bei netz-geaendert, erreichbar ĂŒber âFreies Labor"). Die Werkstatt erweitert diesen Weg, sie erfindet keinen zweiten.
| Gegenstand | Festlegung |
|---|---|
| Datenform | Speicher "sandbox" bleibt {v:1, netz}; neue optionale Felder (name, vorlage) sind erlaubt, alte StĂ€nde tolerieren sie (nie werfen). Die Karriere ("labor") wird nie berĂŒhrt |
| Netzform | unverĂ€ndert {geraete:{id:{âŠ}}, kabel:[âŠ]} wie UI.labor.laden(netz, âŠ); keine neuen GerĂ€tetypen in Welle 1 |
| Module | src/spiel/werkstatt.js â Spiel.werkstatt (reine Logik), src/ui/werkstatt.js â UI.werkstatt (GerĂŒst), src/stil/werkstatt.css; bauen.py lĂ€dt src/spiel/*.js und src/ui/*.js alphabetisch |
| Editor-Mengen | Auswahl-Erweiterung ausschlieĂlich UI-Zustand: Z.auswahl bleibt fĂŒr EinzelgerĂ€t; neu Z.auswahlSet + UI.labor.auswahlMehr(); Duplizieren erzeugt einen Verlaufsschritt, Namen mit Suffix -2/-3, Kabel werden nicht mitdupliziert |
| Minimap | reine Ansicht (SVG-Vorschau), keine Datenform, kein Speicher |
| Wirkung vor GrĂŒn | Das GerĂŒst zĂ€hlt erst als gebaut, wenn es aus dem echten Weg erreichbar ist (UI.sandbox.laden() â Werkstatt-Leiste, Beleg tests/ui-werkstatt.test.js) |
| Nicht in Welle 1 | Vorlagen-Inhalte, Challenge-Karten, PDU/PCAP, Export, neue GerÀtetypen, Zonen |
16.6 Schreibrechte Welle 1 (historischer Auszug)
Ein Schreiber je Datei; die damalige Aufteilung steht im Git-Stand 9716b08^. FĂŒr 3.0.0 gilt die 10-Owner-Ordnung aus AGENTS.md A1.
17 · Welle 2+3 (2.1 S3âS8) â verbindlicher Vertrag (29.10.2026, Stand 2.9.0)
Wiederhergestellt am 31.10.2026 fĂŒr 3.0.0 (Commit 9716b08 hatte § 10â§ 18 gelöscht). Der Abschnitt beschreibt den Vertrag der Welle 2+3; Zahlen stehen ausschlieĂlich in docs/STAND.md, historische Sollzahlen sind als solche benannt.
Anlass. Nach Welle 1 (S1 Wahrheit, S2 Korrektheit) wurde 2.1 zu Ende gebaut: S3 PrĂŒfungsnĂ€he/Generator, S4 Hilfeleiter/Doku-Ziele, S5 Lehrtexte/Fragen/Dichte, S6a/S6b IPv6/Betrieb/WLAN, S7 Volumen/Terminologie, S8 Fassung 2.1.0. Es gilt weiter: ein Schreiber je Datei, keine halbfertigen BeitrĂ€ge, Wirkung vor GrĂŒn.
17.1 Drei neue Fertigkeiten (S6a/S6b) â append-only
| Index | id | Name | ap | Pflichtinhalte |
|---|---|---|---|---|
| 27 | lab.ipv6 | IPv6: Adressen, PrĂ€fixe, NDP | AP1 | Wiki-Seite (â„4 Abschnitte, Quellen), Lehrtext E/AP1/AP2, 6 Minis, 1 Ticket, 1 Hilfe, Senior-Frage, PrĂŒfungsfrage, â„1 Injektor; kein Training (namentliche Ausnahme, docs/STAND.md) |
| 28 | lab.betrieb | Betrieb und Ăberwachung (Syslog, Schwellwert, Backup, Uptime) | AP2 | wie oben, 6 Minis, 1 Training, 1 Ticket, 1 Hilfe, â„1 Injektor |
| 29 | lab.wlan | WLAN: SSID, VLAN, Sicherheit, Gastnetz | AP1 | wie oben, 8 Minis, 2 Tickets, 1 Hilfe, â„2 Injektoren; kein Training (namentliche Ausnahme, docs/STAND.md) |
- AnhĂ€ngeregel: ausschlieĂlich ans Ende von
DATEN.skills; bestehende Indizes 0â26 bleiben unverĂ€ndert.src/spiel/klassenraum-codec.js:TABELLE_SKILLS_LAENGE = 30. Alte Codes bleiben gĂŒltig (Index < 27). - Ehrliche Grenze: IPv6/Betrieb/WLAN werden inhaltlich geĂŒbt (Adressierung, Planung, Konfiguration, Bewertung), nicht paketweise simuliert. Jeder Lehrtext/Wiki-Abschnitt sagt das ausdrĂŒcklich.
- Ăbungsformen ohne Simulation (verbindlich): Wo die Simulation das Thema nicht ausfĂŒhren kann, gibt es kein Training und keinen generierten PrĂŒfungsfall. Stattdessen: Handticket mit Arbeitsziel (
notiz/tabelle/audit/befehl/antwort) auf einem vorhandenen Netz, Minis, Wiki, Lehrtext, Hilfe, Senior-Frage. Fehlende Trainings sind namentliche Ausnahmen indocs/STAND.md. - Neue Sim-GrĂŒnde sind nur zulĂ€ssig, wenn sie in § 5.3 dokumentiert werden und
tests/sim-stand.jsonbewusst aktualisiert wird; Standard ist: keine neuen GrĂŒnde.
17.2 Generator, PrĂŒfung, Abnahme (S3)
| Gegenstand | Festlegung |
|---|---|
| Generierte Tickets | symptomText/briefingText liefern vier Bausteine (Anlass mit Zeit · Beobachtung in Kundensprache · Folge · was schon versucht wurde), 180â420 Zeichen, 2â4 SĂ€tze, 0 Ursachenwörter (docs/entwicklung/Stilfaden â Auftragstexte.md), Titel 15â46 Zeichen, deterministisch ĂŒber Zufall (kein Math.random, kein Datum) |
| Stil-Gate | tools/texte-messen.js --pruefen prĂŒft 100 Seeds je Form (PrĂŒfung, TagesrĂ€tsel, Postfach) und die Handtickets; Exit 1 bei VerstoĂ |
| PrĂŒfung | Alle 30 Fertigkeiten erreichbar (auch Stufe 6 und lab.portsec); Blueprint je AP1/AP2 (Fertigkeitssets), 3 Aufgaben/25 min und IHK-NotenschlĂŒssel unverĂ€ndert |
| Abnahme-Regression | Basis-Paare zusĂ€tzlich zu ICMP: Dienste (HTTP, DNS, DHCP, Druckfreigabe/Port 445) und Drucker-EndgerĂ€te; mindestens 5 Dienst-/Drucker-FĂ€lle in den Tests; REGRESSION_MAX darf dafĂŒr angehoben werden; keine neue Datenform |
PrĂŒfung ist hilfefrei (LRN-01, 3.0.0). Spiel.pruefung.hilfeGesperrt(inst, optionen) ist wahr, wenn optionen.pruefung === true, wenn inst.quelle === "pruefung", oder wenn Spiel.pruefung.aktiv() einen laufenden PrĂŒfungstag liefert. Dann liefern die vier Hilfe-FlĂ€chen leere/null-Werte: Spiel.hilfe.passend â [], Spiel.hilfe.leiter â [], Spiel.hilfe.geruest â null, Spiel.hilfe.syntaxBruecke â null; die Umwege ebenfalls: Spiel.hilfe(inst) â {fehler:"prĂŒfung", grund}, Spiel.vorfuehren(inst) â [], Spiel.vorfuehrenSchritt â null, Spiel.seniorFaellig â false, Spiel.hilfe.anfaengerSchritte â []. AuĂerhalb der PrĂŒfung bleibt alles unverĂ€ndert; die OberflĂ€che darf zusĂ€tzlich {pruefung:true} mitgeben (Konsole/UI setzen das Flag aus Spiel.pruefung.aktiv()). Abnahme: tests/spiel-pruefung-hilfe.test.js â Gegenprobe: Hilfe-Aufrufe in der PrĂŒfung â leer/Fehler; dieselben Aufrufe ohne PrĂŒfungszustand unverĂ€ndert (nicht leer); Flag pruefung:true sperrt auch ohne laufende PrĂŒfung. IHK-Note exakt, ohne Rundung (LRN-03, 3.0.0). Der NotenschlĂŒssel vergleicht exakt: note(p) = 1 ab pâ„92, 2 ab 81, 3 ab 67, 4 ab 50, 5 ab 30, sonst 6 â note(91.99)=2, note(92)=1, note(80.5)=3, note(66.9)=4. Spiel.pruefung.abgeben() speichert den exakten Prozentwert (kein Math.round); gerundet wird nur fĂŒr die Anzeige. Neu: Spiel.pruefung.bestanden(p) = p >= PRUEFUNG.BESTANDEN_AB (exakt: 49,99 % ist nicht bestanden). Kombinationstest 3 Aufgaben Ă 1â6 Ziele: 0 FĂ€lle unter 92 % mit Note 1. Abnahme: tests/pruefung-note.test.js (GrenzfĂ€lle exakt, Kombinationstest, bestanden-Grenze) + bestehende tests/pruefung.test.js bleibt grĂŒn. Fehlende PrĂŒfungsaufgabe zĂ€hlt 0 (LRN-04, 3.0.0). Verschwindet eine PrĂŒfungsinstanz vor der Abgabe (z. B. beim Laden bereinigt), wird sie nicht ĂŒbersprungen: Spiel.pruefung.abgeben() legt einen Teil mit {fehlt:true, anteil:0, bestanden:false} an; der Nenner bleibt fest Spiel.PRUEFUNG.AUFGABEN (3), nicht teile.length. Folge: 2 von 3 gelöst â 66,67 Punkte (Note 4), nicht 100/Note 1; voll gelöst bleibt 100/Note 1. Abnahme: tests/pruefung-abgeben.test.js â voll gelöst 3 Teile/100/Note 1; eine Instanz entfernt â teile.length 3, fehlt:true, punkte †67, Note 4 (Gegenprobe: altes Ăberspringen wĂ€re 100/Note 1).
17.3 Hilfeleiter und Doku-Ziele (S4)
| Gegenstand | Festlegung |
|---|---|
| SENIOR_FRAGEN | SchlĂŒssel = DATEN.skills-id; 30/30 vorhanden |
| Stufe 5 | Ohne eigenes hilfen-Feld kein Lösungstext-Fallback: nur Handlungshinweise (âPrĂŒfe zuerst, ob âŠ"); Stufe 6 bleibt âLösung vorfĂŒhren" |
| Doku-Ziel | Vorhandene Formen notiz (â„ 20 Zeichen) / tabelle / audit wiederverwenden; die 17 Briefings, die Protokoll/Wartungsbuch fordern, bekommen ein Ziel; kein neuer Zieltyp, keine neue Datenform |
| Stufen 5/6 | â„ 10 / â„ 8 Handtickets mit echten Themen (Firewall/Log, NAS-Rechte/Backup, Port-Security in der Halle, âŠ); alle Texte im Stilfaden, Symptom ohne Ursachenwörter |
17.4 Lehrtexte, Fragen, Dichte (S5)
lehrtexte.js: 12 neue Texte â 9 fehlende Fertigkeiten (`lab.switch, lab.subnetz, lab.tcp, lab.cli, lab.speichern, lab.rostick, lab.portfwd, lab.dmz, lab.storage) + 3 neue (lab.ipv6, lab.betrieb, lab.wlan`) â Texte auf 30/30 Fertigkeiten; Quellen nur Whitelist. Datenform (append-only):DATEN.lehrtextebleibt fĂŒr die Sim-Codes unverĂ€ndert; die Skill-Texte kommen als zusĂ€tzliche SchlĂŒssel mit der Skill-id dazu (DATEN.lehrtexte["lab.switch"] = {titel, skill, E, AP1, AP2, quelle}).DATEN.lehrtextFuer(skill, niveau)liefert den Text (Skill-Text hat Vorrang, sonstnull); Consumer ist der Ergebnis-/ErklĂ€rdialog (âErklĂ€r mir das"). Der Dichte-Test zĂ€hlt beide Quellen (Code-Text oder Skill-Text).fragen-vorlagen.js: 20 Vorlagen auf â„15 Fertigkeiten, je â„2 Denkfehler-Familien; Lösung und PrĂŒfung rechnen unabhĂ€ngig (kein Vergleich mit dem Lösungstext), deterministisch.- ZulĂ€ssige Quellen (Content): RFC, IEEE, ISO/IEC, BSI, ETSI, ITIL, SNIA, DSGVO/EU-Recht, Cisco-Dokumentation, IHK-Rahmen (FIAusbV, KMK) â und ausdrĂŒcklich die Wi-Fi Alliance (WPA3-Spezifikation). Der WĂ€chter-Test
tests/wiki-2.0.test.jsakzeptiert âWi-Fi" bereits. tests/inhaltsdichte.test.js: Matrix 30 Fertigkeiten Ă 8 Zeilen â Wiki (â„3 Abschnitte, Ziel â„4), Lehrtext, â„3 Minis, â„1 Training, â„1 Ticket, â„1 Hilfe, â„1 Senior-Frage, â„1 PrĂŒfungsfrage. Ausnahmen nur namentlich mit BegrĂŒndung im Test; Ziel 30/30.
17.5 Volumen, Welle 3 (S7)
Sollzahlen der Welle 3 (Summenprobe): Tickets 76 · Minis 150 · Trainings 52 · Injektoren 55 · jede Fertigkeit â„3 Injektor-Zuordnungen · Hilfe-VorschlĂ€ge 47 · Wiki 37 · Fragen 20/15 · Lehrtexte 46 · Doku-Ziele 17/17. Der gemessene Stand steht ausschlieĂlich in docs/STAND.md. Dazu: Terminologie (Karriere-Stufe vs. ErklĂ€rtiefe vs. Niveau; âBroadcast-DomĂ€ne" einheitlich) dokumentiert; 5 Wiki-Vertiefungsseiten (VPN/IPsec, TLS/Zertifikate, 802.1X, Backup, Virtualisierung) mit â„4 Abschnitten und Quellen; unabhĂ€ngiger Faktencheck 100 Aussagen = 0 Fehler oder benannte Ausnahmen. Jede Einheit mit Lernziel, Quelle, Test.
17.6 Fassung 2.1.0 und Auslieferung (S8) â historisch, ĂŒberholt
Welle 3 zog 2.1.0 (Trockenlauf tools/fassung-ziehen.py --neu 2.1.0, dann --setzen; VERSION_CODE von Hand ĂŒber dem abgeleiteten Wert). Seitdem sind 2.2â2.9 erschienen; die Auslieferung ist in § 23â§ 26 und docs/entwicklung/Ăbergabe â Stand 31.10.2026 â Runde 8.md dokumentiert. FĂŒr 3.0.0 gilt der Fassungszug in docs/SITZUNGSABSCHLUSS.md (3.0.0, VERSION_CODE 30001). Kein Commit, kein Push, keine Veröffentlichung ohne ausdrĂŒckliche Nutzerfreigabe â unverĂ€ndert.
17.7 Zahlenkopplung und Tests
Tests, die ZĂ€hlungen hart codieren, werden auf die Vertragswerte gezogen â ohne AbschwĂ€chung. Neue Tests je Owner in eigenen Dateien; nichts gelöscht; 0 ĂŒbersprungen (Skip-Gate). Die Testzahl und alle ZĂ€hlungen stehen ausschlieĂlich in docs/STAND.md (erzeugt von tools/stand.py); docs/STAND.md wird am Ende jeder Welle neu erzeugt. Handgeschriebene Zahlen in Doku/CHANGELOG sind Befunde.
17.8 Schreibrechte Welle 2 (historisch, 29.10.2026)
Ein Schreiber je Datei; die damalige Owner-Tabelle (O1âO5 + Lead) ist im Git-Stand 9716b08^ nachlesbar (git show 9716b08^:docs/Architektur.md). FĂŒr 3.0.0 gilt die 10-Owner-Ordnung aus AGENTS.md A1 und dem PROMPT-Roster; Kollisionen werden dem Lead gemeldet statt still gelöst.
17.9 Klassenraum-Tabellen und Ticketwachstum (Entscheidung 29.10.2026)
Befund. Das Auftrags-Codec hat 6 Bit Auftragsindex (0..63, Format § 12). 2.1 wuchs von 58 auf 76 Handtickets. Die Tabelle TABELLE_AUFTRAEGE in src/spiel/klassenraum.js ist ein Literal (eingefrorene Bindung); alte Codes bleiben dadurch gĂŒltig, auch wenn neue Tickets an anderer Stelle der lebenden Reihenfolge stehen.
Entscheidung.
- Kein Formatwechsel in 2.1 (keine 7-Bit-Indizes). Die 6-Bit-Grenze bleibt; das Format wird erst in 3.1 angehoben (dann mit Fassungs-/Migrationsweg, vgl. § 12.8).
TABELLE_AUFTRAEGEbleibt unverĂ€ndert die 58 IDs in historischer Reihenfolge;TABELLE_AUFTRAEGE_LAENGE = 58bleibt. Die 2.1-Handtickets sind nicht klassenraum-adressierbar; das wird im Codec-Kopf und in § 12.8 dokumentiert.- Keine Umsortierung bestehender oder neuer Tickets, um eine âlebende Reihenfolge" zu erzwingen.
- Test-Invariante statt Gleichheit: (a) alle 58 IDs existieren genau einmal in
DATEN.tickets, (b) die Tabelle ist 58 lang und duplikatfrei, (c) alte Codes bleiben stabil (Beispielcodes fĂŒr Index 55/56/57 âstorage-01/02/03), (d) Fertigkeitentabelle = 30, (e) die lebendeticketReihe()darf wachsen und wird nicht mit der Codec-Tabelle gleichgesetzt. - Ehrliche Grenze: 3.1 muss vor dem Klassenraum-Feldtest das Format anheben â sonst sind nur 58 von 76 AuftrĂ€gen per Code aufrufbar.
18 · Frontend-Redesign â Gestaltungsvertrag (29.10.2026) â verbindlicher Vertrag
Wiederhergestellt am 31.10.2026 fĂŒr 3.0.0. Der vollstĂ€ndige Gestaltungsvertrag mit allen Farbtabellen, Komponenten- und Shell-MaĂen steht im Git-Stand 9716b08^:docs/Architektur.md und in entwicklung/Design â SpielspaĂ 2.0.md; fĂŒr 3.0.0 gelten die dort festgehaltenen Tokens und Regeln, ergĂ€nzt durch die Abschnitte 21â26.
Anlass. Der Nutzer hat ein âkomplettes Redesign des Frontends" beauftragt: Optik + Shell neu, Bedienlogik und Ansichten bleiben. Basis ist die Bildvorlage A · âMatrix-Elegant" unter Nachweise/Prototypen-Redesign/ (nicht versioniert). Dieser Abschnitt ist die eine Quelle fĂŒr Farbtokens, Typo-Skala, Shell-MaĂe, Komponenten und die Zuordnung zu den Dateien â âVertrag zuerst".
18.1 Geltung und Nicht-Ziele
| Gegenstand | Festlegung |
|---|---|
| Gilt fĂŒr | alle sichtbaren FlĂ€chen des Web-/Windows-/Einzeldatei-Baus: Tokens, Typo, Shell (Kopfzeile, linke Leiste, StatusfuĂ), Karten, Knöpfe, Dialoge, Toasts, NetzflĂ€che, Werkzeuge, alle Ansichten |
| Gilt nicht fĂŒr | Bedienlogik, DOM-VertrĂ€ge, Datenformen, Simulation, Tests mit Verhaltenszusicherung, Inhaltstexte â diese bleiben unangetastet |
| Basis-Vorlage | A · Matrix-Elegant; B (Holo-Suite) und C (Neon-Blueprint) bleiben Stimmungshilfen, kein Vertragsbestandteil |
| Nicht-Ziele | keine neuen Ansichten, keine neuen GerĂ€tetypen, keine neuen Inhalte/Tickets, kein Umbau der Android-Eigenheiten ĂŒber die gemeinsamen Tokens hinaus, keine Ănderung an tests/sim-stand.json |
| Grundsatz | die Vorlage ist Stimmungsvorlage, keine 1:1-Implementierung: Mikrolabels 8â9 px werden auf â„ 11 px gehoben (§ 18.5) |
| Veraltet | § 16.4 bleibt in Kraft (Bewegung, Icon-Regel, Press-Regel) und wird durch § 18.5â18.9 ergĂ€nzt; bei Widerspruch gilt § 18 |
18.5 Typografie
Schriften (unverĂ€ndert, in basis.css und im Projektstamm schriften/): --display: Bricolage Grotesque (600/800 â Wortmarke, H1, Kartentitel, Knöpfe), --body: Atkinson Hyperlegible (400/700 â FlieĂtext, Zeilen), --mono: JetBrains Mono (400/600 â Mikrolabels, IPs, Messwerte, Statuszeile). Keine neuen Schriftdateien, keine Netzschriften.
| Token | px | Verwendung |
|---|---|---|
--fs-1 | 11 | Mikrolabels (Mono, Versalien, Sperrung .08em), SVG-Beschriftung, Chips, Statuszeile â Untergrenze fĂŒr funktionale Beschriftung |
--fs-2 | 12.5 | dichte Zeilen, Tabellen, Hinweise |
--fs-3 | 14 | Standard-FlieĂtext in Karten und Formularen |
--fs-4 | 16 | Grundtext (body) |
--fs-5 | 20 | Kartentitel, AbschnittsĂŒberschriften |
--fs-6 | 28 | H2/Ansichtstitel |
--fs-7 | 34 | H1 klein / groĂe Zahlen |
--fs-8 | 44 | H1 der Vorlage A (44/46) |
Regeln: Funktionale Beschriftung nie unter 11 px (Gate ui-typografie.test.js). Mono nur fĂŒr Daten (IPs, Ports, Messwerte, Status, Mikrolabels), nicht fĂŒr FlieĂtext. 13 px und 15 px sind Altlasten aus v2.1.0 â ethos.py erlaubt sie bis zur Umstellung, neu entstehen dĂŒrfen sie nicht. H1/H2 mit line-height: 1.1â1.15, text-wrap: balance; Satzbreite max. 72 Zeichen je Spalte, Karten mit FlieĂtext â„ 320 px.
21 · Runde 3b â Audit-Umsetzung, Ambient und lebende Zonen (30.10.2026) â verbindlicher Vertrag
Auftrag (Nutzer, wörtlich). âdann fixe diese doch, warum hast du das nicht gleich gemacht?" (bezogen auf die 13 Audit-Signale), âWenn ein PC aus der Zone geschoben wird muss die zone sich mit ihm verschieben", âes fehlen noch so die ganzen ambient, atmenden, pulsierenden effekte". Beleg: Nachweise/Prototypen-Runde3/audit/NOTIZ.md (13 Signale mit Messwerten) und audit-umsetzung/NOTIZ.md.
Umsetzung (Dateien, je ein Schreiber).
- hub.css / karriere.css / spiel.css: Heute-DatenflĂ€chen 10 â 2 (Rahmen gesamt 12 â 7, Rest = Buttons), Typo-Hierarchie statt 11-px-Monokultur (Anteil <= 12,5 px: 87 % â 77 %, >= 14 px: 12 â 22 Knoten), Messing-Versalien 41 â 0; Lernstand 119 â 79 Rahmen, Kompetenzbereich kartenlos; Shop von 4 auf 2 Rahmen, Regal zweispaltig, Akzentwand weg; Einstellungen als eine SegmentflĂ€che; Postfach-Neon-Reste (Kundenkante, Icon-Platte, Pillen) entfernt, Kacheln gestreckt, Aktionsleiste 44 px unten.
- zz-polaris.css / editor.css / inspektor.css / basis.css: die Doppel-SchrÀgstrich-Labels aus allen UI-Beschriftungen entfernt (Ersatz: 2-px-Akzentlinie oder Satzlabel); Labor-Kopf Kunde unter dem Titel (gemessen: POLARIS 1440 Titel 253 x 39 / Kunde 103 x 18, Abstand 4 px, kein Overlap; Messing 231 x 36 / 101 x 16); Versalien reduziert, Sperrsatz <= .06em; neue Tokens --dauer-atmen 5000 ms, --dauer-licht 6000 ms, --dauer-lauflicht 700 ms.
- Theme-Dateien: Ambient sichtbar (FlĂ€che atmet: Messing 9 s, Pulsar 7 s + Sternenpunkte, Keramik 12 s, Nordlicht Aurora, Papier Papierkorn; Deckkraft .12â.20), Interaktion: Dock-Knopf atmet, PrimĂ€rknopf- Schein, Auswahlring pulsiert 320 ms, Live-Kabel mit laufendem Lichtpunkt; âBewegung aus" = Standbild (basis.css zentral). Heute-Kontrastblock fĂŒr alle Themes (Messing 8,38:1, Pulsar 12,29:1, Keramik 5,93:1, Nordlicht 14,18:1, Papier 6,83:1; alle >= 4,5:1); Ursache der schwarzen Messing-Knöpfe war eine Ambient-Sheen-Regel, die die PrimĂ€rflĂ€che ĂŒberschrieb â behoben (Schein animiert nur box-shadow).
- editor.js: Zonen wandern live â geraetBewegt() ruft zonenNeu(); HĂŒlle und Chip werden wĂ€hrend des Ziehens pro Frame neu berechnet, die Zone folgt dem GerĂ€t bzw. wĂ€chst mit.
- Leisten-Labels (Runde 3a) und Umlaute bleiben unverĂ€ndert gĂŒltig.
Gates (letzte Messung). 873/873 Tests, ethos GRĂN (R4 169, R6 7, R9 58, R10 40 â besser als Stand), klassen 0, umlaute GRĂN, Rauchtest 45/45 (letzter Lauf), starttest GRĂN. R12 nicht gemessen (--dom). Ambient im Build per CDP-Frames belegt (16,5 % Pixel sichtbar verĂ€ndert; âBewegung aus" = 0 Animationen).
22 · Bento 2.5 âArbeitsplatz" (30.10.2026) â verbindlicher Gestaltungsvertrag
Auftrag (Nutzer, sinngemĂ€Ă). Postfach âlieblos und unpersönlich", Kunden âzu kindlich statt Job-Simulation mit Kundenprofil", Dashboard âeinzelnes unordentliches Objekt" (Ziele, Sitzung, Klassenraum), Training âKaskade an Einheiten" und âaltes Design", minimierte Leiste âkomplett ĂŒberarbeiten: viel interaktiver, bessere und innovativere Inhalte"; dazu der Hilfeleiter-Slogan aus dem ersten Screenshot. Ziel: eine ruhige, dichte ArbeitsoberflĂ€che eines IT-Dienstleisters in Bento-Form â echte Hierarchie, glaubwĂŒrdige Daten, wenig Farbe, keine Emoji-Deko, keine Riesen-Slogans in FunktionsflĂ€chen.
Vertrag. Vollfassung: Entwurf â Bento 2.5 (Arbeitsplatz).md. Die LeitsĂ€tze:
- Bedienlogik, Datenwege und Spielzustand bleiben unverĂ€ndert. Keine Ănderung an
src/sim/,src/modell/,src/kern/,src/cli/; Ansichten rufen dieselbenSpiel.*-Wege. - Test-Haken bleiben (Klassen und Bedeutung): `.sp-postfach/.sp-pf-liste/.sp-leser/.sp-mail/ .sp-brief/.sp-knoepfe/.sp-blatt/.sp-hilfe/.sp-leiter
,.hb-seite,.kr-seite/.kr-kopf/ .kr-raster/.kr-kunde/.ka-block,.tr-seite/.tr-liste/.tr-karte/.tr-geruest/.tr-quelle/ .tr-ergebnis/.tr-offen/.tr-grund/.tr-lernen/.tr-fehler*`, Leisten-Haken siehe Testspiel-leiste-hilfe.test.js(.lk-zeilemit exakter Kindfolge,.lk-auf,.lk-mini,.lk-mini-hilfe,Mini-Zeichen, Hilfe-Knopf). - Nur Tokens. Neue FlĂ€chen nutzen ausschlieĂlich
basis.css-Tokens. Schrift nurvar(--fs-*), Abstand nur {0,4,8,12,16,24,32}px, Radius nur Tokens, keine Farb-Literale, kein!importantauĂerhalbbasis.css(Gates:tools/ethos.py,tools/klassen.py,ui-typografie,ui-symbole). Die fĂŒnf Theme-Dateienzzz-thema-*.cssbleiben unangetastet; bestehende Klassennamen bleiben, damit ihre Regeln weiter greifen. - Keine erfundenen Zahlen, keine Emoji in der UI-Chrome. Anzeige nur aus
Spiel.*/DATEN; Kunden-Emoji (DATEN.kunden[*].symbol) wird in den KundenflÀchen durch Monogramme ersetzt. - Gemeinsame Bausteine der Leitung:
src/stil/bento.css(12-Spalten-Raster.bn-raster, Kachel.bn-kmit Variantenflach|akzent|iris|geld, `.bn-kopf/.bn-kicker/.bn-titel/.bn-lead/ .bn-meta/.bn-fuss/.bn-zeile/.bn-wert/.bn-chips/.bn-chip/.bn-marke/.bn-trenner/.bn-leer`, Spalten.bn-s3âŠs12) undsrc/ui/bento.js(UI.bento.monogramm/initialen/kachel/kopf). - Ein Schreiber je Datei. Postfach:
spiel.js+spiel.css; Kunden:karriere.js(nur Kunden/Kundenakte) +karriere.css+kundenakte.css; Dashboard:hub.js+hub.css; Training:training.js+training.css; Leiste:leiste.js+leiste.css; Grundlage und Integration: Leitung (bento.*, Doku, Werkzeuge, Nachweise). - Abnahme je Bereich: private Vorschau
python tools/vorschau.py --name <bereich>+python Nachweise/bento/foto.py --html vorschau-<bereich>.html --marke <bereich>(Bilder 1440/960/720), SichtprĂŒfung, mindestens zwei Iterationen;node tests/run.jsgrĂŒn,klassen.py0,ethos.pyGRĂN (R4/R9/R10 dĂŒrfen nicht steigen),umlaute.pyGRĂN. - Nicht Teil des Umbaus: neue Ansichten, Ănderungen an Simulation/Netz/Codec/Spielstand, Themes, Fassung, Push/Release. Die Leitung committet, zieht Doku/Stand nach und legt die Abnahme vor.
Stand (30.10.2026). Umgesetzt von fĂŒnf parallelen Bereichsteams (ein Schreiber je Datei), danach von der Leitung integriert und von zwei unabhĂ€ngigen PrĂŒfungen (Design; Theme/Struktur) gegengelesen. Gates: node tests/run.js 878/878 grĂŒn, 0 ĂŒbersprungen · sh tools/test.sh --rauch 45/45 · python tools/klassen.py 0 · python tools/ethos.py GRĂN (R2 20â19, R4 169â153, R9 62â58, R10 42â39) · python tools/umlaute.py GRĂN · node tools/sim-stand.js unverĂ€ndert · python tools/seite.py --pruefen GRĂN (54 Dokumente). SichtprĂŒfung je Bereich bei 1440/960/720, Theme-PrĂŒfung 7 ZustĂ€nde Ă 7 Ansichten ohne harten Kontrastbruch, 0 Emoji in src/ui/**/src/stil/**. Nicht gemessen: R12 (--dom), Lauf in Android, Hover-/TastaturzustĂ€nde im Vollbild. Die Windows-.exe wurde dagegen gebaut, mit tools/q-echt.py echt bedient und gestartet. Fassung 2.5.0 ist gezogen; Zweig ausbau-1.2, Tag v2.5.0 und GitHub-Release wurden am 30.10.2026 mit ausdrĂŒcklicher Nutzerfreigabe veröffentlicht. Patch 2.5.1 (31.10.2026) verkĂŒrzt den Zuklapp-Timer der Leiste von 5 s auf 1 s. Einzelheiten: Ăbergabe â Stand 30.10.2026 â Bento 2.5.
23 · Runde 4 â Jobdoku, Flugschreiber, Fenster (31.10.2026) â verbindlicher Vertrag
Auftrag (Nutzer, sinngemĂ€Ă). Die noch alten FlĂ€chen (Verdacht/Akte, Lernstand, Simulator, Inspektor, Shop, Fenster, Interaktion) sollen eine eigenstĂ€ndige Bento-2.5-Sprache bekommen und zu einer glaubwĂŒrdigen Job-Simulation mit didaktischer Hilfe professionalisiert werden â kreativ neu gedacht, nicht poliert; ausgeliefert als Fassung 2.6.0 inklusive Push, Tag, Release und Live-Seite. Vollfassung: Entwurf â Runde 4 (Jobdoku & QoL).
Neue und getrennte OberflÀchen-Dateien (Phase 0, Verhalten unverÀndert).
src/ui/lernstand.js(UI.lernstand: Ansicht, Kompetenzkarte, PrĂŒfungstag, Training) undsrc/ui/shop.js(UI.shop: Werkstattregal, Kauf) sind aussrc/ui/karriere.jsausgelagert;src/stil/lernstand.cssundsrc/stil/shop.cssauskarriere.css.UI.karriere._helferstelltoverlaybereit (fĂŒr die ausgelagerten Ansichten).UI.karriere.lernstandAnsicht/shopAnsicht/training/pruefungUebersicht/pruefungAbgebenbleiben als Alias erhalten. Ansicht-IDs (lernstand,shop) undUI.startHakenunverĂ€ndert.src/ui/naechster.js(UI.naechster) undsrc/ui/fehlerziel.js(UI.fehlerziel) neu.
Datenformen und Schnittstellen (nur additiv).
Spiel.akte:Spiel.akte.schichten(inst) â [{n, name, text, karten}],Spiel.akte.fundort(karte, inst) â ziel|null,Spiel.akte.fortschritt(inst) â {âŠ},Spiel.akte.bericht(inst) â Text (ohne Lösung);Spiel.akte.FORTSCHRITT= die sechs Stufen (beobachten · messen · eingrenzen · Hypothese · handeln · dokumentieren).UI.fehlerziel.springen(ziel) â {ok, schritte, grund, ort}mitziel = {art:"geraet"|"port"|"kabel"|"frame"|"ebene"|"zone", geraet, port, kabel:{a,b}|id, frame, ebene, werkzeug, dock, leise, ms, zentrieren}: EIN Resolver fĂŒr Akte, Simulations-Panel und Inspektor; öffnet Labor, wechselt Ebene, wĂ€hlt GerĂ€t/Port/Kabel, zeigt den Inspektor, zentriert und hebt hervor. Er wirft nie, ruft keinfocus(), respektiertUI.bewegung(). Hilfen:zielAusEreignis,kabelEnden,ortText,istProblem.einst.anfaengerHilfe: bool(store"einst"). Fehlt der Wert, gilt Standard an bei Karrierestufe †2, sonst aus. Lese-/Schreib-API:UI.simpanel.anfaengerHilfe()undUI.simpanel.anfaengerHilfeSetzen(bool); Bedienung im Panel-Kopf und in den Einstellungen (src/ui/karriere.js, Abschnitt âLernen").Spiel.hilfe.anfaengerSchritte(inst)undSpiel.naechsterOrt(inst): additive Anzeigehilfen (keine Ănderung an Schwellen, Vorrat oder Bezahlwegen).
OberflÀche (PrÀfixe bleiben, Testhaken bleiben).
- Einsatzakte
ak-(Reiterakteim Dock;.ak-karten/.ak-karte/.ak-verdacht/.ak-chipbleiben), Ausbildungs-Cockpitlern-/nst-/kk-(.kr-kopf/.kr-seitebleiben), Flugschreibersi-(.si-kopf,.si-warn,.si-hilfeneu;.si-zeilebleibt), Inspektorin-(Steckbriefe;[data-reiter]bleibt), Fensterkopffenster-knopfinsrc/stil/rahmen.css(nurPlattform.name === "tauri"), Werkstattregalwr-/kr-ware. - Fenster:
shell/src-tauri/src/fenster.rsschaltet im Modus âvoll"set_decorations(false)(Leiste/Tray unverĂ€ndert) und bietet den Befehlfenster_knopf("minimieren"|"maximieren"|"schliessen");src/ui/app.jsbaut die Kopfzeile mitdata-tauri-drag-region="deep"und drei 44-px-Knöpfen. src/stil/bento.css:.bn-s12/.bn-k.breitfallen im â€-900-px-Raster auf eine Spalte zurĂŒck (Reparatur des 720-px-Befunds aus Strang B).
Gates (gemessen 31.10.2026). node tests/run.js 930/930 grĂŒn, 0 ĂŒbersprungen · sh tools/test.sh --rauch 45/45 · klassen.py 0 · ethos.py GRĂN (R1 23, R2 17, R4 140, R9 58, R10 42) · umlaute.py GRĂN · sim-stand.js unverĂ€ndert · Bau 138 Module. SichtprĂŒfung/Rollen: fĂŒnf Bereichs-Owner, zwei unabhĂ€ngige PrĂŒfungen (Design; Theme/Struktur/A11y), Leitung integriert, committet und veröffentlicht. Nicht gemessen: R12 (--dom), Android-APK, echte Maus im ausgelieferten Tauri-Fenster (PrĂŒfbau mit eigener Kennung, weil eine fremde Instanz lief). Einzelheiten: Ăbergabe â Stand 31.10.2026 â Runde 4.
24 · Runde 5 â Auftragsbogen, Inspektor, Leiste als Begleiter (31.10.2026) â verbindlicher Vertrag
Auftrag (Nutzer, sinngemĂ€Ă). Nach 2.6.0 meldet der Nutzer drei Bildschirmfotos zurĂŒck: âunordentlich ĂŒberlappende Elemente", âmanche Elemente sind nicht richtig ausgerichtet", die Minileiste âvermittelt visuell nicht ihren Nutzen, sondern overwhelmed", das Zusammenfallen der Leiste mĂŒsse âschöner animiert" werden, und âdie AuftrĂ€ge zu simulieren im Minimodus funktioniert nicht richtig, da viel zu wenig Kontext herrscht ⊠das Konzept muss umgedacht werden". Verlangt: Full Skill Usage und Agenten-SchwĂ€rme. Vollfassung: Entwurf â Runde 5 (Leiste & Auftragsmappe).
Auftragsbogen (A).
UI.spiel.mappeAuf(reiter="brief", erstes=false, mitFokus=true)â optionaler dritter Parameter;mappeZu()unverĂ€ndert. Fokus geht in die Mappe und beim SchlieĂen zurĂŒck zum Auslöser.- Zustand: solange die Mappe offen ist, trĂ€gt
.labordie Klassemappe-an;editor.cssblendet.lb-leiste-oben,lb-leiste-untenund.lb-leinwand::beforeaus (visibility:hidden, Platz bleibt) und hebt.lb-auftragaufz-index:30. Die Klasse wird bei jedem Neuzeichnen gesetzt/entfernt (SchlieĂen, Ticketende, Ansichtswechsel). - Die Mappe bleibt Kind von
#labor-auftrag(Testhaken unverĂ€ndert); Höchsthöhemax-height:var(--am-max, min(70vh,560px))(spiel.js setzt--am-maxaus dem freien Platz),--am-linksrichtet die Blattkante an der Auftragszeile aus; â€900 px als Blatt von unten. - Der frĂŒhere Kopfraum-Hack (
--am-kopfraum/padding-top) ist entfallen; die--leiste-h-Messung in editor.js bleibt.
Karte (D). Knoten-Labels: Baselines Name/Rolle/IP bei ikonH+18/33/48 (15 px), Box bis +62, line-height:1.15; Decluttering (labelUeber) gilt auch fĂŒr .ger-ip; die Adresse erscheint nur bei Hover/Auswahl oder Zoom â„ IP_ZOOM (0,85).
Inspektor (B). Kopf kompakt (Mikrolabel · Name · Rollenzeile · eine Kennzahlenzeile). Ports: 2-zeiliger Block je Port (Zeile 1 Name + Link-Chip + Modus-Chip; Zeile 2 zwei gleich breite Felder Modus/VLAN mit je einer Beschriftung, â„44 px); die Portliste scrollt in sich, ihre Höhe wird auf ganze Portzeilen gerundet (rAF + ResizeObserver) â keine angeschnittene Zeile. Die SVI-Verwaltungskarte liegt im VLAN-Reiter.
Mini-Leiste (C) â Konzept âBegleiter". Die Leiste löst keine AuftrĂ€ge; sie begleitet:
- zugeklappt 300Ă56: Stufe · Rolle vollstĂ€ndig, drei Werte in einer Zeile (
âŹ/h · Ruf · offen), Deckkraft Standard0.92(store "einst".leiste.deckkraft, geklemmt 0,6â1; Regler in app.js 60â100 %), Hover 1; - aufgeklappt 320Ă300: âJetzt dran" (Kunde · Auftrag + nĂ€chster Schritt aus
Spiel.naechsterOrt/Spiel.naechsteDiagnose, PrimĂ€r âIm Labor fortsetzen", SekundĂ€r âAuftrag lesen" ĂŒberUI.spiel.mappeAuf), âKurzĂŒbung (freiwillig)" (bestehende Mini-Frage mit Kontextzeile aus dem aktiven Auftrag), âLage" (VertrĂ€ge/Serie/offen), Schnellaktionen; - Animation: 320 ms
cubic-bezier(.22,1,.36,1)fĂŒr Höhe/Breite, Inhalt gestaffelt 40/80/120 ms; Zuklappen Inhalt zuerst, Karte danach, FenstergröĂe (Plattform.fenster.groesse) erst nach der Karten-Animation; âBewegung aus" = sofort. MaĂe und.lk-*-Haken bleiben Testvertrag.
Gates (gemessen 31.10.2026). node tests/run.js 932/932 grĂŒn, 0 ĂŒbersprungen · Rauchtest 45/45 · klassen.py 0 · ethos.py GRĂN (R1 23, R2 17, R3 4, R4 135, R9 58, R10 42; R12 nicht gemessen) · umlaute.py GRĂN · Simulation unverĂ€ndert · 138 Module. Ein Test wurde umgestellt (nicht abgeschwĂ€cht): tests/ui-leiste-ueberlappung.test.js prĂŒft die neue Mechanik (mappe-an/visibility:hidden statt Kopfraum) mit 6 statt 4 FĂ€llen. Nicht gemessen: R12 (--dom), Android-APK, echte Maus im ausgelieferten Tauri-Fenster (nur PrĂŒfbau), Screenreader.
25 · Runde 6/7 â KurzĂŒbung mit Kontext und Flugschreiber-Aufmerksamkeit (31.10.2026)
Runde 6 (KurzĂŒbung). Neues Modul src/spiel/kurzfrage.js: Spiel.kurzfrage.fuer(inst, {niveau}) â {id,titel,frage,lage:[{label,wert}],optionen:[{text,richtig}], erklaerung,ort} | null. FĂŒnf Vorlagen (netz/maske/gateway/portmodus/zustand) aus inst.netz, Modell.adressen, Sim.adresse, IP.*, g.running.ports/.if, Kabeln und g.an; deterministisch aus inst.iid (Zufall ohne Uhr); genau eine richtige Option; Niveau E/AP1/AP2; Lage 2â4 Zeilen; kein Lösungstext (Fehlerstellen nach Spiel.plan.diff ausgeklammert). Die Leiste (src/ui/leiste.js, leiste.css) rendert bei offenem Auftrag diese Frage mit Lage-Block; ohne verwertbares Netz zeigt sie keine Frage, sondern den Weg ins Labor. Das generische Mini-Ticket bleibt als KopfĂŒbung im Dialog (karriere.js/mk-einordnung).
Runde 7 (Flugschreiber). src/ui/simpanel.js + simulation.css: eine Lesespalte mit drei Ebenen â Ergebnis (Urteil 28 px, eine Zahlenzeile, Segmentleiste), Befund (erster Fehler als 2-px-Rail-Zeile, ein ErklĂ€rungssatz, leiser Weg âZum Fundort â"), nĂ€chster Schritt (numerierte Hilfe, hĂ€ngender Einzug, leiser Verweis âGerĂ€t zeigen â"). Höchstens EIN gefĂŒllter Hauptweg je Zustand, alle ĂŒbrigen Ghost 32 px; sichtbare 44-px-TrefferflĂ€chen fĂŒr Transport/Filter bleiben unsichtbar erhalten. Hilfe an/aus als Ghost-Schalter, Format als Textsegment. Testhaken .si-*, Transport, Zeitachse, UI.simpanel.anfaengerHilfe und PDU unverĂ€ndert.
Messung (31.10.2026). Runde 6: 950/950 Tests in der abgetrennten PrĂŒfkopie; 228/228 Fragen ĂŒber 76 Handtickets Ă 3 Niveaus; Lösungstext-Scan 0 Treffer. Runde 7: Dock 380 px, laute Knöpfe 11â2, primĂ€re 3â0 (Fehler+Hilfe an), FlĂ€chen 4â1; 10â2/3â0/3â1 (Hilfe aus); 6â2/4â1/2â0 (Ergebnis); Ăberlauf 0; Mikrolabel 6,08:1. Im gemeinsamen Arbeitsbaum 1033/1033 grĂŒn (dieser Commit enthĂ€lt nur Runde-6/7-Dateien). R12 nicht gemessen.
26 · Runde 8 â Abteilung âSubnetting" (31.10.2026) â verbindlicher Vertrag
Auftrag (Nutzer, wörtlich). âNetLab braucht ein Subnetting abteil wo das intensiv beigebracht, gelehrt mit visuellen Hilfselementen und Ăbungsaufgaben und tools / spielchen sind nur zum lernen von subnetting und vLAN. ⊠wie das am besten in die bestehenden themes integriert wird und auch wo, ich denke ein eigenes abteil wird dem gerecht. Generell sollen alle Aufgaben Inhaltlich und vom Lernschwierigkeitsgrad optimiert werden. Mache das vollstĂ€ndig fertig und melde dich wenn der build und deploy fertig ist." Vollfassung: Entwurf â Runde 7 (Abteilung Subnetting).
Neue Ansicht subnetz (âSubnetting"). Eigene Registrierung ĂŒber UI.startHaken + UI.app.registrieren("subnetz", âŠ); Dock-Reihenfolge in app.js REIHE direkt nach âlabor" (UI.app.liste() ĂŒberspringt unbekannte Namen, der Eintrag ist damit auch ohne Ansicht unschĂ€dlich). Vier Register: Lehrpfad (8 Module), Werkzeuge (6), Ăben & Spielen (Generator + 3 Lernspiele), Formelsammlung. Kein Geld, kein Ruf, kein Spielstand-Feld.
Datenformen (nur additiv, headless).
Spiel.subnetz(src/spiel/subnetz.js):VERSION,STUFEN(E|AP1|AP2),ARTEN(11 Typen: `netzid, broadcast, hosts, maske, block, subnetze, vlsm, zusammenfassen, vlan-port, vlan-erreichbar, trunk),rechnen(ip, praefix),maskeZuPraefix,praefixZuMaske`,gleichesNetz,plan(basis, bedarf),zusammenfassen(netze),aufgabe({art, niveau, seed}),satz({arten?, niveau, anzahl, seed}),pruefen(aufgabe, antwort),antwort(aufgabe, richtig, {hilfe?, spiel?}),fortschritt(),modulSetzen(id, status),zuruecksetzen(). Deterministisch ĂŒber einen gesĂ€ten Generator; keinMath.random, keine Uhr, kein Datum.- Aufgabe: `{id, art, niveau, skill, titel, frage, lage[], eingabe{art, optionen?}, loesung{wert, text}, rechenweg[{n, titel, text, ergebnis}], erklaerung, hinweis, visual{art, preset}, quelle}`.
rechenwegendet inloesung.wert; genau eine richtige Antwort; alle Angaben inlage. DATEN.subnetzLehrplan(src/daten/subnetz-lehrplan.js):version,formelsammlung[],module[8](feste Reihenfolgebits, maske, block, netzid, schneiden, vlsm, vlan, pruefung) mit `stufe, titel, kicker, lead, kern[], merksatz, visuell{art, preset}, beispiel[{titel, aufgabe, schritte[], ergebnis}], uebung{arten, niveau, anzahl}, pruefung{arten, niveau, anzahl, bestehen}, verweise{wiki, werkzeug, training}, dauerMin, quelle`.UI.subnetzwerkzeuge(src/ui/subnetz-werkzeuge.js):liste()(6 Werkzeuge), `zeichnen(container, id, opts)â{zerstoeren()},spiele.liste()(3 Spiele),spiele.starten(container, id, opts)`. Werkzeuge:bitlabor, maskenleiste, blocklineal, rechner, vlsm, vlan. Spiele: `praefix-blitz, netz-sortierer, vlan-patcher`.- Eigener Speicher
store "subnetz"v:1, ausschlieĂlich ĂŒber die API: `{v:1, module:{[id]:{status:"neu"|"gelesen"|"geuebt"|"sicher", richtig, falsch, serie}}, spiele:{[id]:{bestwert, runden}}, gesamt:{richtig, falsch, serie}}.Spiel.st` (v:3) bleibt unverĂ€ndert â keine Migration, alte StĂ€nde bleiben gĂŒltig.
Lernen und Verbuchung. AusschlieĂlich ĂŒber L.ueben(skill, ok, {hilfe}) / L.fehler("labor-subnetz", skill, text); Werkzeuge, die die Lösung zeigen, verbuchen {hilfe:true} (Karte steigt nicht). Module: neu < gelesen < geuebt < sicher; âsicher" = PrĂŒfrunde bestanden (â„4 von 5, ohne Hilfe); ein Fehler senkt âsicher" auf âgeuebt", nie unter âgelesen". Kein Geld/Ruf.
Lernstufen (verbindlich, PrĂŒfmaĂstab). E: /24â/28, letztes Oktett, 1 Schritt, hinweis immer; AP1: /20â/30, 2â3 Schritte; AP2: /16â/31, 3â5 Schritte, optional Zeitdruck. VLSM: E â, AP1 2â3 Netze, AP2 4â5 Netze inkl. Reserve. VLAN: E ein Access-Port, AP1 zwei Ports/Erreichbarkeit, AP2 Trunk/Tagging/ Native/allowed. Formulierungsregeln und die Vorher/Nachher-Liste der optimierten Bestandsaufgaben: Subnetting â Lernstufen-Matrix und Entwurf § 10.
Optimierte Bestandsaufgaben (belegt, Tests grĂŒn). vlan-netzgroesse lehrt jetzt Hosts je VLAN â PrĂ€fix statt â/24 auf n VLANs"; mini-sub-1/mini-sub-3 Stufen getauscht (Netzadresse /26 = AP1, Hosts /28 = E; Stufen-Multiset unverĂ€ndert); mini-trunk-5 mit ausdrĂŒcklichem Native-Kontext; Denkhilfen fĂŒr mini-vlan-6, mini-trunk-5, mini-rostick-4; doppelte Trainings unterscheidbar (tr-trunk-erlauben/tr-trunk-allowed, tr-netz-maske/tr-netz-nachbar); tr-subnetz-plan auf E-Niveau gestĂŒtzt (Teiltabelle) ohne die Format-Regel niveau = f(art) zu brechen.
EinfĂŒgung in die Themes. Nur Tokens aus basis.css und Bausteine bento.css (.bn-raster/.bn-k/âŠ); eigene Klassen sn-* (Abteilung), sw-* (Werkzeuge), sg-* (Spiele). Alle 5 Zusatz-Themes + hell/dunkel/system bei 1440/960/720 geprĂŒft; VLAN-Farben --vlan-1âŠ8 nur mit Nummer; 44-px-Ziele scopen (global .knopf = 40 px); Fokusring zentral, kein eigener Reduced-Motion-Block; keine Emoji.
EinfĂŒgung in die Navigation. Dock (REIHE, Kurzform âSubnetz" gegen den Wortumbruch bei 76 px), Befehlspalette (automatisch ĂŒber UI.app.liste(), dazu sechs Befehle âSubnetting: Werkzeug âŠ"), tools/rauch.py (ein Fall âSubnetting" â 48/48), Querverweis am Ende der Trainingsliste (âZur Abteilung Subnetting") und interne Verweise zu Wiki (UI.wiki.oeffnen), Labor und Training. Kein Hub-Umbau: das âHeute"-Raster hat eine feste 12-Spalten-Belegung; eine zusĂ€tzliche Kachel hĂ€tte die gemessene Bento-Ordnung gebrochen (im Entwurf § 2.5 als ânur wenn messbar Platz frei" vorgesehen, hier bewusst verworfen).
Nicht Teil der Runde: neue DATEN.skills (30 â Codec- und Dichte-Tests eingefroren), neue Tickets, Ănderungen an spiel/injektoren.js/spiel/mini.js-Mechanik/Lernmotor, src/sim|modell|kern|cli.
Stand (gemessen am 31.10.2026). node tests/run.js 1049/1049 grĂŒn, 0 ĂŒbersprungen (118 Testdateien, 98 headless Module) · Rauchtest 48/48 · klassen.py 0 · ethos.py GRĂN (R1 23, R2 17, R3 4, R4 134, R5 0, R6 7, R7 0, R8 0, R9 58, R10 41, R11 0; R12 nicht gemessen) · umlaute.py GRĂN · Simulation unverĂ€ndert · Bau 143 Module (98 headless) · Einzeldatei 4.080.274 B, SHA256 49fc2fa5âŠ, docs/index.html und Netzwerk-Labor.html byte-gleich · Doku 65 Dokumente, 0 tote Verweise, seite.py/stand.py --pruefen grĂŒn · SichtprĂŒfung 1440/960/720 Ă 7 Theme-ZustĂ€nde: 0 seitlich scrollende Seiten, 0 JS-Fehler, 0 KontrastverstöĂe, âBewegung aus" = 0 Animationen (3 abgeschnittene Textknoten nur in der VLSM-Balkenbeschriftung kleiner Blöcke â die Legende nennt den vollen Namen; Bit-Zellen sind die begrĂŒndete Ausnahme der 44-px-Regel). Fassung 2.9.0 (VERSION_CODE 20901), veröffentlicht mit Tag v2.9.0. Einzelheiten: docs/STAND.md, CHANGELOG, Ăbergabe â Stand 31.10.2026 â Runde 8 und Subnetting â Lernstufen-Matrix. Nicht gemessen: R12 (--dom), Android-APK, Screenreader, echte Maus im ausgelieferten Tauri-Fenster.
27 · Fassung 3.1.0 â Onboarding, Analyse-Leitfaden, Labor-Aktionen, Layout (01.11.2026)
Analyse-Leitfaden (Variante B). Prozessmodell ist die vollstĂ€ndige Handlung: klĂ€ren â Lage/Schichten â Hypothese â messen â eingreifen/prĂŒfen â dokumentieren/lernen. Neue Dateien: src/daten/leitfaden.js (Texte), src/spiel/leitfaden.js (Erkennung), src/ui/leitfaden.js (Panel). Schnittstelle: Spiel.leitfaden.stand(inst) â {pruefung, fortschritt, aktuell, schritte:[{id, titel, status, impuls}]}, naechsterSchritt(inst) â {id, titel, text} | null, zuruecksetzen(inst). Die Erkennung nutzt ausschlieĂlich vorhandene echte SpielzustĂ€nde (Auftrag, angesehene Lage/GerĂ€te, Hypothese/Verdacht, Messungen, Eingriffe, Verifikation, Abnahme) und fĂŒhrt keine neuen Fortschrittsspeicher ein, soweit ableitbar. In der PrĂŒfung (Spiel.pruefung.aktiv()) liefert die Engine nur einen neutralen Status ohne Impulse; Prozesshilfe kostet keine Sterne. Das Panel ist Text (keine neuen Bedienelemente in der Labor-Ansicht).
Onboarding. src/ui/onboarding.js zeigt beim ersten Start ohne offenen Auftrag vier Schritte aus DATEN.leitfaden.tour; Zustand einst.tour = {gesehen: true}; ĂŒberspringbar (Esc), wiederholbar ĂŒber Einstellungen. Die Tour rendert als body-level Overlay (auĂerhalb .ansicht) und verĂ€ndert die R12-ZĂ€hlung nicht.
Labor-Aktionen. Die rechte Leiste macht die Kernaktionen sichtbar: Auftrag/Abnahme, Messen (Ping), Konsole, GerĂ€t/Inspektor, Akte/Dokumentieren, Hilfe/Leitfaden. Umsetzung durch Umgruppieren vorhandener Funktionen und body-level MenĂŒs (keine Doppelung): die rechte Leiste zeigt die Kernaktionen als Textliste (Name + TastenkĂŒrzel), ausfĂŒhrbar ĂŒber das bestehende, bereits gezĂ€hlte MenĂŒ (Kopf âAktionen", body-level, zĂ€hlt nicht). Bei 960/720, wo Auftragszeile/⯠nicht sichtbar sind, öffnet ein einzelner sichtbarer Kopfknopf dasselbe MenĂŒ; das erhöht die R12-Zahl der Labor-Ansicht bei diesen beiden Breiten um genau +1 (11â12, 7â8). Diese Ausnahme ist Lead-freigegeben und wird ĂŒber das dokumentierte Baseline-Update-Verfahren (Nachweise/3.1/gates/02-âŠ) mit BegrĂŒndung âKernaktionen sichtbar/erreichbar", altâneu-Protokoll und Reviewer-SĂ€tzen (r-design, r-arch) eingefroren; alle ĂŒbrigen R12-Werte bleiben unverĂ€ndert, klassenraum bleibt 6/6/6/6. Das Leitfaden-Panel ist Text und immer sichtbar, solange ein Auftrag lĂ€uft â kein neuer Toggle.
Layout. Inspektorbreite per Pointer-Drag auf der Trennlinie, persistiert in einst.layout.inspBreite (Grenzen 280â560, Default 360, Doppelklick = Default); Tastatur ĂŒber Alt+â/â. Der Griff ist role="separator" (kein [role=button], kein input) und damit kein R12-Bedienelement; er bleibt per Tastatur bedienbar.
QualitĂ€t. Neue Gates: tests/leitfaden*.test.js (Erkennung, PrĂŒfungsmodus, Determinismus), tests/onboarding-tour.test.js, tests/labor-aktionen.test.js, tests/layout-breite.test.js, tests/kein-leck.test.js (keine Lösungen in Texten/ZustĂ€nden vor der Aktion). Alle neuen Tests sind auto-Teil der Suite; R12/44-px-Grenzen und der Detektor bleiben unverĂ€ndert.
Nicht gemessen (3.1.0, ehrlich). R12-Neumessung und Sicht erst im 3.1-Gate; APK/SHA256SUMS bleiben ohne Android-Keystore-Secrets rot (BAU-02). GerÀte-/Screenreader-Test weiterhin offen.