&$.ausloeser==="frosttreffer"&&l.element==="frost"&&(r.schubStapel=g.min($.maxStapel,r.schubStapel+1))}function fn(r,l,c,k){const b=n.gegner[r.art],v=[];b.ruestungMilli>0&&v.push(ct("gegner.ruestung",b.ruestungMilli)),b.bannPromille>0&&l.element!==Rt&&v.push(dt("gegner.bann",b.bannPromille));const $=k===1e3?c.schadenMilli:g.permille(c.schadenMilli,k);Fe(r,ut($,v)),dn(r,l)}function Be(){for(let r=p.length-1;r>=0;r-=1){const l=p[r];if(l.hpMilli>0)continue;p.splice(r,1);const c=n.kopfgeldJeWelle[m]??0;h+=S>0?g.max(1,g.idiv(g.mulI(c,n.gegner[l.art].kopfgeldAnteilPromille),S)):0}}function hn(){for(const r of E){if(r.abklingTicks>0){r.abklingTicks-=1;continue}const l=n.tuerme[r.art],c=l.stufen[r.stufe],k=n.brett.bauplaetze[r.slot].position,b=p.filter(N=>de(N,l.zielt)&&te(N,k,c.reichweiteMilli)),v=r.zielwahl===q.erster?N=>-N.fortschrittMilli:r.zielwahl===q.entferntester?N=>N.fortschrittMilli:r.zielwahl===q.staerkster?N=>-N.hpMilli:N=>De(N,k),$=Oe(b,v);if(!$)continue;for(const N of cn(l,c,$,b,k))fn(N.ziel,l,c,N.anteilPromille);r.abklingTicks=c.taktTicks;const ie=C($.pfad,$.fortschrittMilli);d.push({id:t.naechste(),vonX:k.xMilli,vonY:k.yMilli,zuX:ie.xMilli,zuY:ie.yMilli,restTicks:ye})}Be()}function gn(){for(const r of p)r.brandRestTicks<=0||(Fe(r,g.idiv(g.mulI(r.brandProSekundeMilli,r.brandStapel),pe)),r.brandRestTicks-=1,r.brandRestTicks===0&&(r.brandStapel=0));for(const r of me(p,l=>l.id)){if(r.hpMilli<=0)continue;const l=le(n.gegner[r.art].eigenschaften??[],"heilaura");if(!l)continue;const c=g.idiv(l.heilungProSekundeMilli,pe),k=C(r.pfad,r.fortschrittMilli);for(const b of p)b===r||b.hpMilli<=0||b.brandRestTicks>0&&b.brandBlockiertHeilung||te(b,k,l.radiusMilli)&&(b.hpMilli=g.min(b.maxHpMilli,b.hpMilli+c))}for(const r of p)r.schildMaxMilli>0&&(r.schildMilli=g.min(r.schildMaxMilli,r.schildMilli+r.schildRegenJeTickMilli)),r.langsamRestTicks>0&&(r.langsamRestTicks-=1,r.langsamRestTicks===0&&(r.langsamPromille=0));Be()}function mn(){for(let r=d.length-1;r>=0;r-=1){const l=d[r];l.restTicks-=1,l.restTicks<=0&&d.splice(r,1)}}function pn(){if(M<=0){u=I.niederlage;return}u===I.welle&&(z.length>0||p.length>0||(m+=1,u=m>=n.wellen.length?I.sieg:I.bau))}const Se={get tick(){return f},get phase(){return u},get gold(){return h},get kernpunkte(){return M},get welleNummer(){return m+1},absicht(r,l){i.anfuegen(f,r,l)},step(){if(!(u===I.sieg||u===I.niederlage))try{ln(),an(),on(),hn(),gn(),mn(),pn(),f+=1}catch(r){throw yn(r)}},vorspulen(r){if(!Number.isInteger(r)||r<0)throw new R(P.invarianteVerletzt,"vorspulen erwartet eine Anzahl ab 0.",{kontext:{ticks:String(r)}});for(let l=0;l{const l=C(r.pfad,r.fortschrittMilli);return{id:r.id,x:l.xMilli,y:l.yMilli,art:r.art,hpPromille:g.toMilli(g.div(g.fromMilli(g.max(0,r.hpMilli)),g.fromMilli(r.maxHpMilli)))}})},turmListe(){return E.map(r=>{const l=n.brett.bauplaetze[r.slot];return{id:r.id,slot:r.slot,x:l.position.xMilli,y:l.position.yMilli,art:r.art,stufe:r.stufe,zielwahl:r.zielwahl}})},geschossListe(){return d.map(r=>({id:r.id,vonX:r.vonX,vonY:r.vonY,zuX:r.zuX,zuY:r.zuY,fortschrittPromille:g.idiv(g.mulI(ye-r.restTicks,1e3),g.max(1,ye))}))},bauplaetze(){return n.brett.bauplaetze.map((r,l)=>{const c=E.find(k=>k.slot===l);return{slot:l,frei:c===void 0,optionen:n.tuerme.map((k,b)=>{const v=c?k.stufen[c.stufe+1]?.kosten??0:k.stufen[0].kosten;return{turm:b,kosten:v,erlaubt:c===void 0&&h>=v}})}})},hashFelder(){const r=[["tick",f],["phase",u],["gold",h],["kernpunkte",M],["welle",m],["gegnerAnzahl",p.length],["ausstehend",z.length],["idNaechste",t.sichern().naechste]];for(const l of[...p].sort((c,k)=>c.id-k.id))r.push([`g${l.id}`,`${l.art}:${l.fortschrittMilli}:${l.hpMilli}:${l.schildMilli}:${l.brandRestTicks}:${l.brandStapel}:${l.langsamRestTicks}:${l.langsamPromille}:${l.schubStapel}`]);for(const l of[...E].sort((c,k)=>c.id-k.id))r.push([`t${l.id}`,`${l.art}:${l.slot}:${l.stufe}:${l.abklingTicks}:${l.zielwahl}`]);return r},hash(){return kt(Se.hashFelder())},intentLog(){return i}};return Se}function Nt(e,n,t){if(!Number.isInteger(e)||e<0)throw new R(P.invarianteVerletzt,"Eine Kurve hat keine gültige Schrittzahl.",{kontext:{schritte:String(e)}});const i=[];let s=n;for(let a=0;aa===i);if(s===void 0)throw new ne(t,`einer von [${n.join(", ")}] erwartet, war "${i}"`);return s}function ve(e,n,t){const i=e[n];if(i===void 0)throw new ne(t,`Eintrag ${n} fehlt`);return i}const je=1e3,It=["boden","luft"],Dt=[Ee.standard,Ee.hoehensockel,Ee.grabensteg];function Me(e,n){const t=ze(e,n);return{x:we(t.xMilli,`${n}.xMilli`)/je,y:we(t.yMilli,`${n}.yMilli`)/je}}function Lt(e,n){const t=ze(e,n),i=V(t.wegpunkte,`${n}.wegpunkte`);if(i.length<2)throw new Error(`${n}.wegpunkte: mindestens zwei Wegpunkte`);return{id:Q(t.id,`${n}.id`),art:en(t.art,It,`${n}.art`),wegpunkte:i.map((s,a)=>Me(s,`${n}.wegpunkte[${a}]`))}}function Ft(e,n,t){const i=ze(e,t);return{slot:n,id:Q(i.id,`${t}.id`),sorte:en(i.sorte,Dt,`${t}.sorte`),position:Me(i.position,`${t}.position`)}}function Bt(e,n){const t=V(e,"bretter");if(t.length===0)throw new Error("bretter: leer");const i=ve(t,0,"bretter");if(i===void 0)throw new Error('bretter: Brett "" nicht gefunden');const s=ze(i,"brett"),a=Q(s.id,"brett.id"),o=V(s.bauplaetze,`${a}.bauplaetze`).map((h,M)=>Ft(h,M,`${a}.bauplaetze[${M}]`)),f=new Map(o.map(h=>[h.id,h.slot])),u=V(s.bindeglieder,`${a}.bindeglieder`).map((h,M)=>{const m=V(h,`${a}.bindeglieder[${M}]`),p=f.get(Q(ve(m,0,`${a}.bindeglieder[${M}]`),"slot-id")),E=f.get(Q(ve(m,1,`${a}.bindeglieder[${M}]`),"slot-id"));if(p===void 0||E===void 0)throw new Error(`${a}.bindeglieder[${M}]: verweist auf einen unbekannten Bauplatz`);return[p,E]});return{id:a,spalten:we(s.spalten,`${a}.spalten`),reihen:we(s.reihen,`${a}.reihen`),tor:Me(s.tor,`${a}.tor`),eingaenge:V(s.eingaenge,`${a}.eingaenge`).map((h,M)=>Me(h,`${a}.eingaenge[${M}]`)),pfade:V(s.pfade,`${a}.pfade`).map((h,M)=>Lt(h,`${a}.pfade[${M}]`)),bauplaetze:o,bindeglieder:u}}const Gt=`[
{
"id": "board.die-enge",
"_note": "Layout A aus 01-kampfebene.md 2.3: Scharmuetzel-Lernbrett, 10 x 14 Felder, 8 Slots, eine Route als S-Kurve. Koordinaten in Feld-Milli: xMilli 5000 ist die Mitte von Spalte 5. Der Luftpfad steht nicht im Original-ASCII - er ist hier ergaenzt, weil die Aschekraehe (GDD 5.3) sonst kein Brett haette, auf dem sie fliegen kann; Flieger ziehen stur geradeaus zum Tor.",
"spalten": 10,
"reihen": 14,
"tor": { "xMilli": 8000, "yMilli": 14000 },
"eingaenge": [{ "xMilli": 5000, "yMilli": 1000 }],
"pfade": [
{
"id": "path.enge-boden",
"_note": "Wegpunkte sind die Ecken der S-Kurve, nicht jede Kachel: (5,1) runter bis (5,4), rechts bis (9,4), runter bis (9,7), links bis (2,7), runter bis (2,10), rechts bis (8,10), runter zum Tor (8,14).",
"art": "boden",
"wegpunkte": [
{ "xMilli": 5000, "yMilli": 1000 },
{ "xMilli": 5000, "yMilli": 4000 },
{ "xMilli": 9000, "yMilli": 4000 },
{ "xMilli": 9000, "yMilli": 7000 },
{ "xMilli": 2000, "yMilli": 7000 },
{ "xMilli": 2000, "yMilli": 10000 },
{ "xMilli": 8000, "yMilli": 10000 },
{ "xMilli": 8000, "yMilli": 14000 }
]
},
{
"id": "path.enge-luft",
"_note": "Zwei Wegpunkte, weil Flieger alle Bodenkurven ignorieren. Ein Sprite und eine Bewegungsregel - und trotzdem eine komplett eigene Verteidigungsanforderung.",
"art": "luft",
"wegpunkte": [
{ "xMilli": 5000, "yMilli": 1000 },
{ "xMilli": 8000, "yMilli": 14000 }
]
}
],
"bauplaetze": [
{
"id": "slot.enge-a",
"sorte": "hoehensockel",
"position": { "xMilli": 3000, "yMilli": 2000 }
},
{ "id": "slot.enge-b", "sorte": "standard", "position": { "xMilli": 7000, "yMilli": 2000 } },
{ "id": "slot.enge-c", "sorte": "standard", "position": { "xMilli": 3000, "yMilli": 5000 } },
{
"id": "slot.enge-d",
"sorte": "grabensteg",
"position": { "xMilli": 6000, "yMilli": 6000 }
},
{
"id": "slot.enge-e",
"sorte": "grabensteg",
"position": { "xMilli": 5000, "yMilli": 8000 }
},
{ "id": "slot.enge-f", "sorte": "standard", "position": { "xMilli": 7000, "yMilli": 8000 } },
{
"id": "slot.enge-g",
"sorte": "hoehensockel",
"position": { "xMilli": 4000, "yMilli": 11000 }
},
{ "id": "slot.enge-h", "sorte": "standard", "position": { "xMilli": 6000, "yMilli": 12000 } }
],
"bindeglieder": [
["slot.enge-a", "slot.enge-c"],
["slot.enge-b", "slot.enge-d"],
["slot.enge-c", "slot.enge-e"],
["slot.enge-d", "slot.enge-f"],
["slot.enge-e", "slot.enge-g"],
["slot.enge-f", "slot.enge-h"],
["slot.enge-g", "slot.enge-h"]
]
}
]
`,_t=`[
{
"id": "enemy.zunder-schwarm",
"_note": "STARTWERTE, GDD 5.3: 20 x 18 HP, 1,8 Felder/s. Ueberlastet Einzelziel-Tuerme. hpMilli ist die HP EINER Einheit; \`gruppengroesse\` sagt, wie viele davon eine Gruppe sind.",
"familie": "masse",
"hpMilli": 18000,
"tempoMilliProSekunde": 1800,
"ruestungMilli": 0,
"bannPromille": 0,
"gruppengroesse": 20,
"kopfgeldAnteilPromille": 15,
"bewegtSichAuf": "boden",
"eigenschaften": [],
"antworten": [
"tower.glutwacht",
"tower.funkenhorn",
"endform.aschekessel",
"endform.kettenschlag"
],
"falscheAntworten": ["endform.jagdspeer"]
},
{
"id": "enemy.wallbrecher",
"_note": "PHASE-0-KALIBRIERUNG, gemessen mit \`pnpm balance:messen\`. GDD 5.3: 2.400 HP, 30 flache Ruestung. Seine gueltigen Antworten sind ausschliesslich Endformen (Schmelzofen, Jagdspeer, Erdungsstab), die es in Phase 0 nicht gibt - mit den Kanonwerten war er rechnerisch unverwundbar. Auch 900/12 blieben unloesbar: Bei 4 bis 18 Turmschaden frisst Ruestung 12 zwischen 33 und 85 Prozent jedes Treffers, und KEIN Bauplan kam ueber Welle 8. 350 HP und 6 Ruestung lassen die Ruestung ihre Rolle behalten - Aufwertungen sind die Antwort - ohne ihn immun zu machen. Ab AP-1.10 zurueck Richtung GDD.",
"familie": "panzer",
"hpMilli": 350000,
"tempoMilliProSekunde": 800,
"ruestungMilli": 6000,
"bannPromille": 0,
"gruppengroesse": 1,
"kopfgeldAnteilPromille": 120,
"bewegtSichAuf": "boden",
"eigenschaften": [],
"antworten": ["endform.schmelzofen", "endform.jagdspeer", "endform.erdungsstab"],
"falscheAntworten": ["endform.bolzenbank"]
},
{
"id": "enemy.schlundgeist",
"_note": "STARTWERTE, GDD 5.3: 480 HP, heilt Verbuendete 40/s in Radius 2,0, 0,9 Felder/s. Die zweite Antwort ist die Kette: Sie erreicht den Heiler mitten im Pulk, ohne dass ein Turm freie Sicht auf ihn braucht.",
"familie": "stoer",
"hpMilli": 480000,
"tempoMilliProSekunde": 900,
"ruestungMilli": 0,
"bannPromille": 0,
"gruppengroesse": 1,
"kopfgeldAnteilPromille": 60,
"bewegtSichAuf": "boden",
"eigenschaften": [
{
"art": "heilaura",
"heilungProSekundeMilli": 40000,
"radiusMilli": 2000
}
],
"antworten": ["tower.glutwacht", "endform.aschekessel", "endform.kettenschlag"],
"falscheAntworten": ["endform.bolzenbank"]
},
{
"id": "enemy.frostbrand-zelot",
"_note": "STARTWERTE, GDD 5.3: 520 HP, verlangsamungsimmun, +40 % Tempo nach jedem Frosttreffer. Antwort ist Verwurzelung (Fusion Nagelschuss) oder die Frostwacht stilllegen - der kostenlose Knopf im Turm-Panel. Ohne ihn waere dieser Gegner unfair.",
"familie": "stoer",
"hpMilli": 520000,
"tempoMilliProSekunde": 1100,
"ruestungMilli": 0,
"bannPromille": 0,
"gruppengroesse": 1,
"kopfgeldAnteilPromille": 65,
"bewegtSichAuf": "boden",
"eigenschaften": [
{
"art": "verlangsamungsimmun"
},
{
"art": "temposchub",
"ausloeser": "frosttreffer",
"bonusPromille": 400,
"maxStapel": 5
}
],
"antworten": ["fusion.nagelschuss", "tower.steinwacht", "endform.erdungsstab"],
"falscheAntworten": ["endform.frostgruft"]
},
{
"id": "enemy.aschekraehe",
"_note": "PHASE-0-KALIBRIERUNG, gemessen. GDD 5.3: 8 x 190 HP, 2,2 Felder/s, Luftkorridor. Der Luftweg ist 13,3 Felder lang und war damit in 6 Sekunden durchflogen - 1.520 Lebenspunkte in 6 Sekunden verlangen rund 250 Schaden je Sekunde, und Welle 7 kostete in JEDER Messung alle acht Durchbrueche. 70 HP und 1,5 Felder/s machen daraus eine Pruefung statt einer Mauer. Glutwacht und Schutthorn koennen sie physisch nicht treffen - hier ist nur die Glutwacht im Minimalsatz.",
"familie": "flug",
"hpMilli": 70000,
"tempoMilliProSekunde": 1500,
"ruestungMilli": 0,
"bannPromille": 0,
"gruppengroesse": 8,
"kopfgeldAnteilPromille": 25,
"bewegtSichAuf": "luft",
"eigenschaften": [],
"antworten": ["tower.steinwacht", "tower.frostwacht", "tower.funkenhorn"],
"falscheAntworten": ["tower.glutwacht"]
},
{
"id": "enemy.banntraeger",
"_note": "PHASE-0-KALIBRIERUNG, gemessen. GDD 5.3: 900 HP, Bann 65 %, Schild 400 (regeneriert). Bann wirkt NUR auf Feuer/Frost/Blitz/Arkan - Stich geht durch; die Simulation wandte ihn bis AP-0.16 auf jeden Turm an, womit die Steinwacht als vorgesehene Antwort keine war. Mit korrektem Bann und 400 HP / 150 Schild ist er in Phase 0 loesbar. Der Deckel 700 Promille steht im Schema, damit kein Content-Patch daraus eine Immunitaet macht.",
"familie": "stoer",
"hpMilli": 400000,
"tempoMilliProSekunde": 1000,
"ruestungMilli": 0,
"bannPromille": 650,
"gruppengroesse": 1,
"kopfgeldAnteilPromille": 90,
"bewegtSichAuf": "boden",
"eigenschaften": [
{
"art": "schild",
"punkteMilli": 150000,
"regenerationProSekundeMilli": 20000
}
],
"antworten": ["endform.erdungsstab", "tower.steinwacht", "endform.jagdspeer"],
"falscheAntworten": ["tower.glutwacht"]
}
]
`,Ht=`[
{
"id": "curve.wellen-hp",
"_note": "HP-Skalierung je Welle in PROMILLE: 1000 = Grundwert, 1025 = +2,5 %. PHASE-0-KALIBRIERUNG, gemessen. Der Kanon (01-kampfebene.md 6) nennt 1,145, also x4,4 auf Welle 12 - gerechnet gegen den VOLLEN Werkzeugkasten aus Endformen, Fusionen, Affixen, Draft und Ausruestung. In Phase 0 waechst die Spielermacht nur ueber drei Aufwertungsstufen und acht Bauplaetze; mit 1,145 kam kein Bauplan ueber Welle 8. Ab AP-1.10 zurueck Richtung Kanon. Ausgewertet wird iterativ ueber N.scaleMilli, ein Schritt je Welle - nie als Potenz. Die Sim multipliziert die Gegner-HP einmal beim Spawn.",
"schritte": 12,
"einheit": "promille",
"kurve": {
"kind": "geometric",
"base": 1000,
"factorPermille": 1025
}
},
{
"id": "curve.kopfgeld",
"_note": "Wellenkopfgeld in Gold, aus 01-kampfebene.md 6: Kopfgeld(w) = (40 + 12w) x 1,13^(w-1), Richtung Null abgeschnitten. Als \`table\` und nicht \`geometric\`, weil die Formel ein Produkt aus einem LINEAREN und einem geometrischen Anteil ist; eine geometrische Naeherung waere eine stille Abweichung vom Kanon. ACHTUNG: Im Kanon wachsen die HP schneller als das Einkommen, und diese Schere IST der Schwierigkeitsgrad. In Phase 0 steht die HP-Kurve auf 1,025, die Schere laeuft also andersherum; der Anstieg kommt aus der Wellenzusammensetzung. Beide Kurven nur GEMEINSAM bewegen.",
"schritte": 12,
"einheit": "gold",
"kurve": {
"kind": "table",
"values": [52, 72, 97, 126, 163, 206, 258, 319, 393, 480, 583, 705]
}
}
]
`,xt=`[
{
"id": "tower.steinwacht",
"_note": "STARTWERTE ZUM KALIBRIEREN. Grundwerte aus GDD 5.2: 90 Gold, 14 Schaden, 0,55 s Takt, Reichweite 3,2. 0,55 s sind bei 30 Hz 16,5 Ticks - aufgerundet auf 17 (0,567 s, 24,7 DPS statt 25,5). Aufwertung nach Kanon: +55 % Schaden, +0,2 Reichweite, Kosten 0,8x und 1,6x Basis, Endform 2,6x Basis.",
"element": "stich",
"zielt": "boden-und-luft",
"zielwahl": "erster",
"botRolle": "einzelziel",
"stufen": [
{
"stufe": 1,
"kosten": 90,
"schadenMilli": 14000,
"taktTicks": 17,
"reichweiteMilli": 3200
},
{
"stufe": 2,
"kosten": 72,
"schadenMilli": 21700,
"taktTicks": 17,
"reichweiteMilli": 3400
},
{
"stufe": 3,
"kosten": 144,
"schadenMilli": 33635,
"taktTicks": 17,
"reichweiteMilli": 3600
}
],
"wirkungen": [],
"endformen": [
{
"id": "endform.bolzenbank",
"_note": "3er-Salve a 60 % (GDD 5.2). Ergibt 180 % Gesamtschaden gegen ein Ziel, verteilt aber auf drei Treffer - gegen flache Ruestung ist das deutlich schlechter als ein grosser Treffer. Genau das macht Wallbrecher zum Raetsel.",
"kosten": 234,
"wirkungen": [
{
"art": "salve",
"schuesse": 3,
"anteilPromille": 600
}
]
},
{
"id": "endform.jagdspeer",
"_note": "1,8 s Aufladung = 54 Ticks, +180 % gegen das Ziel mit den meisten HP (GDD 5.2).",
"kosten": 234,
"wirkungen": [
{
"art": "aufladung",
"ladezeitTicks": 54,
"bonusPromille": 1800,
"ziel": "meiste-hp"
}
]
}
]
},
{
"id": "tower.glutwacht",
"_note": "STARTWERTE. GDD 5.2: 110 Gold, 12 Schaden + Brand 6/s ueber 3 s, Takt 0,80 s = 24 Ticks, Reichweite 2,5, Splash 0,9, nur Boden. Der Brand blockiert Heilung vollstaendig - das ist seine zweite Rolle und die Antwort auf den Schlundgeist.",
"element": "feuer",
"zielt": "boden",
"zielwahl": "erster",
"botRolle": "flaeche",
"stufen": [
{
"stufe": 1,
"kosten": 110,
"schadenMilli": 12000,
"taktTicks": 24,
"reichweiteMilli": 2500
},
{
"stufe": 2,
"kosten": 88,
"schadenMilli": 18600,
"taktTicks": 24,
"reichweiteMilli": 2700
},
{
"stufe": 3,
"kosten": 176,
"schadenMilli": 28830,
"taktTicks": 24,
"reichweiteMilli": 2900
}
],
"wirkungen": [
{
"art": "splash",
"radiusMilli": 900
},
{
"art": "schaden-ueber-zeit",
"schadenProSekundeMilli": 6000,
"dauerTicks": 90,
"maxStapel": 1,
"blockiertHeilung": true
}
],
"endformen": [
{
"id": "endform.aschekessel",
"_note": "Einschlaege hinterlassen 4 s (120 Ticks) brennende Pfadflaechen mit 14/s (GDD 5.2). Zonenverweigerung.",
"kosten": 286,
"wirkungen": [
{
"art": "splash",
"radiusMilli": 900
},
{
"art": "schaden-ueber-zeit",
"schadenProSekundeMilli": 6000,
"dauerTicks": 90,
"maxStapel": 1,
"blockiertHeilung": true
},
{
"art": "pfadflaeche",
"dauerTicks": 120,
"schadenProSekundeMilli": 14000,
"radiusMilli": 900
}
]
},
{
"id": "endform.schmelzofen",
"_note": "Brand stapelt bis 5x, je Stapel -4 Ruestung (GDD 5.2). Gegen Wallbrecher (Ruestung 30) traegt das 20 Ruestung ab.",
"kosten": 286,
"wirkungen": [
{
"art": "splash",
"radiusMilli": 900
},
{
"art": "schaden-ueber-zeit",
"schadenProSekundeMilli": 6000,
"dauerTicks": 90,
"maxStapel": 5,
"blockiertHeilung": true
},
{
"art": "ruestungsabbau",
"betragMilli": 4000,
"maxStapel": 5,
"bedingung": "brennend"
}
]
}
]
},
{
"id": "tower.frostwacht",
"_note": "PHASE-0-KALIBRIERUNG, gemessen. GDD 5.2: 80 Gold, 4 Schaden, Verlangsamung 35 % / 2 s, Takt 1,00 s, Reichweite 2,8 als 60-Grad-Kegel. Damit war sie eine Falle: Wer zwei Steinwachten durch zwei Frostwachten ersetzt, verliert 41 Schaden je Sekunde - und in Phase 0 gibt es nichts, was Kontrolle in Schaden verwandelt (Splitterfrost, Nagelschuss fehlen noch). 7 Schaden (halbe Steinwacht), 45 % / 3 s und 90 Grad geben ihr einen eigenen Boden. Ab AP-1.10 zurueck Richtung GDD. 90 Grad sind 1024 Binaergrad.",
"element": "frost",
"zielt": "boden-und-luft",
"zielwahl": "erster",
"botRolle": "kontrolle",
"stufen": [
{
"stufe": 1,
"kosten": 80,
"schadenMilli": 7000,
"taktTicks": 30,
"reichweiteMilli": 3000
},
{
"stufe": 2,
"kosten": 64,
"schadenMilli": 10850,
"taktTicks": 30,
"reichweiteMilli": 3200
},
{
"stufe": 3,
"kosten": 128,
"schadenMilli": 16817,
"taktTicks": 30,
"reichweiteMilli": 3400
}
],
"wirkungen": [
{
"art": "kegel",
"oeffnungBinaergrad": 1024
},
{
"art": "verlangsamung",
"staerkePromille": 450,
"dauerTicks": 90
}
],
"endformen": [
{
"id": "endform.frostgruft",
"_note": "Verlangsamung 60 %, Radius 2,4 STATT Kegel (GDD 5.2). Der fehlende \`kegel\`-Eintrag ist die Aussage: Endform-Wirkungen ersetzen die Grundwirkungen vollstaendig.",
"kosten": 208,
"wirkungen": [
{
"art": "radiuswirkung",
"radiusMilli": 2400
},
{
"art": "verlangsamung",
"staerkePromille": 600,
"dauerTicks": 60
}
]
},
{
"id": "endform.splitterfrost",
"_note": "Verlangsamte Ziele erleiden +30 % Schaden aus allen Quellen (GDD 5.2). Kegel und Grundverlangsamung bleiben und stehen deshalb wieder mit drin.",
"kosten": 208,
"wirkungen": [
{
"art": "kegel",
"oeffnungBinaergrad": 683
},
{
"art": "verlangsamung",
"staerkePromille": 350,
"dauerTicks": 60
},
{
"art": "schadensfaktor",
"anteilPromille": 300,
"bedingung": "verlangsamt"
}
]
}
]
},
{
"id": "tower.funkenhorn",
"_note": "PHASE-0-KALIBRIERUNG, gemessen. GDD 5.2: 130 Gold, 18 Schaden, Kette 3 mit -35 % je Sprung, Takt 1,40 s, Reichweite 3,0. Damit lag sein Schaden je Gold bei 40 Prozent der Steinwacht, und ein Bauplan mit Funkenhorn kam nie so weit wie derselbe ohne. Kette 4 mit -25 % bringt ihn auf Augenhoehe. \`spruenge\` zaehlt GETROFFENE ZIELE, das erste eingeschlossen. Ueberlaedt Schilde statt sie abzutragen.",
"element": "blitz",
"zielt": "boden-und-luft",
"zielwahl": "erster",
"botRolle": "flaeche",
"stufen": [
{
"stufe": 1,
"kosten": 130,
"schadenMilli": 18000,
"taktTicks": 42,
"reichweiteMilli": 3000
},
{
"stufe": 2,
"kosten": 104,
"schadenMilli": 27900,
"taktTicks": 42,
"reichweiteMilli": 3200
},
{
"stufe": 3,
"kosten": 208,
"schadenMilli": 43245,
"taktTicks": 42,
"reichweiteMilli": 3400
}
],
"wirkungen": [
{
"art": "kette",
"spruenge": 4,
"abfallPromille": 250
}
],
"endformen": [
{
"id": "endform.kettenschlag",
"_note": "6 Spruenge, -20 % je Sprung (GDD 5.2).",
"kosten": 338,
"wirkungen": [
{
"art": "kette",
"spruenge": 6,
"abfallPromille": 200
}
]
},
{
"id": "endform.erdungsstab",
"_note": "Einzelziel, x3 Schaden (= Zuschlag 2000 Promille), ignoriert Bann vollstaendig (GDD 5.2). Dass hier keine \`kette\` steht, IST der Einzelziel-Teil: Endform-Wirkungen ersetzen die Grundwirkungen.",
"kosten": 338,
"wirkungen": [
{
"art": "schadensfaktor",
"anteilPromille": 2000,
"bedingung": "immer"
},
{
"art": "bannbruch",
"anteilPromille": 1000
}
]
}
]
}
]
`,Kt=`[
{
"id": "wave.enge-01",
"_note": "Rolle Einfuehrung (01-kampfebene.md 6): ein Gegnertyp, kein Raetsel. Alle Zeiten in Ticks bei 30 Hz - 18 Ticks Abstand sind 0,6 s, die Welle laeuft rund 24 s bei 1x.",
"brett": "board.die-enge",
"nummer": 1,
"prueft": "einfuehrung",
"pauseNach": [],
"gruppen": [
{
"gegner": "enemy.zunder-schwarm",
"anzahl": 14,
"startTick": 0,
"abstandTicks": 18,
"pfad": "path.enge-boden"
}
]
},
{
"id": "wave.enge-02",
"_note": "Weiter Einfuehrung, dichter. Erster Draft danach - Kanon GDD 4.",
"brett": "board.die-enge",
"nummer": 2,
"prueft": "masse",
"pauseNach": ["draft"],
"gruppen": [
{
"gegner": "enemy.zunder-schwarm",
"anzahl": 24,
"startTick": 0,
"abstandTicks": 12,
"pfad": "path.enge-boden"
}
]
},
{
"id": "wave.enge-03",
"_note": "Erstes Raetsel: Wallbrecher, flache Ruestung. EIN Wallbrecher und nicht zwei - gemessen kostete die Doppelung einem mittelmaessigen Bau 8 der 20 Kernpunkte, und das auf der Welle, die die Mechanik erst einfuehrt. Die Verdopplung steht in Welle 5. Beutetisch 1 (Kanon: Beutetische nach 3/7/11).",
"brett": "board.die-enge",
"nummer": 3,
"prueft": "ruestung",
"pauseNach": ["beutetisch"],
"gruppen": [
{
"gegner": "enemy.wallbrecher",
"anzahl": 1,
"startTick": 0,
"abstandTicks": 120,
"pfad": "path.enge-boden"
},
{
"gegner": "enemy.zunder-schwarm",
"anzahl": 16,
"startTick": 60,
"abstandTicks": 14,
"pfad": "path.enge-boden"
}
]
},
{
"id": "wave.enge-04",
"_note": "Zweites Raetsel: Heilung. Der Schlundgeist macht jeden fast toten Zunder wieder ganz - Brand blockiert das, mehr Rohschaden nicht.",
"brett": "board.die-enge",
"nummer": 4,
"prueft": "heilung",
"pauseNach": ["draft"],
"gruppen": [
{
"gegner": "enemy.zunder-schwarm",
"anzahl": 22,
"startTick": 0,
"abstandTicks": 11,
"pfad": "path.enge-boden"
},
{
"gegner": "enemy.schlundgeist",
"anzahl": 2,
"startTick": 120,
"abstandTicks": 90,
"pfad": "path.enge-boden"
}
]
},
{
"id": "wave.enge-05",
"_note": "Mischwelle: zwei Raetsel gleichzeitig (Ruestung und Heilung). Orakel danach - Kanon GDD 4: Orakel nach 5 und 9.",
"brett": "board.die-enge",
"nummer": 5,
"prueft": "gemischt",
"pauseNach": ["orakel"],
"gruppen": [
{
"gegner": "enemy.wallbrecher",
"anzahl": 2,
"startTick": 0,
"abstandTicks": 150,
"pfad": "path.enge-boden"
},
{
"gegner": "enemy.zunder-schwarm",
"anzahl": 20,
"startTick": 30,
"abstandTicks": 12,
"pfad": "path.enge-boden"
},
{
"gegner": "enemy.schlundgeist",
"anzahl": 2,
"startTick": 90,
"abstandTicks": 120,
"pfad": "path.enge-boden"
}
]
},
{
"id": "wave.enge-06",
"_note": "Mini-Boss-Rolle: eine Elite mit genau einer Mechanik. Der Banntraeger bestraft Elementar-Monokultur.",
"brett": "board.die-enge",
"nummer": 6,
"prueft": "bann",
"pauseNach": ["draft"],
"gruppen": [
{
"gegner": "enemy.banntraeger",
"anzahl": 1,
"startTick": 0,
"abstandTicks": 0,
"pfad": "path.enge-boden"
},
{
"gegner": "enemy.zunder-schwarm",
"anzahl": 22,
"startTick": 30,
"abstandTicks": 11,
"pfad": "path.enge-boden"
}
]
},
{
"id": "wave.enge-07",
"_note": "Erste Luftwelle. Wer nur Glutwacht gebaut hat, sieht hier zum ersten Mal, dass sie physisch nicht treffen kann. Beutetisch 2.",
"brett": "board.die-enge",
"nummer": 7,
"prueft": "luft",
"pauseNach": ["beutetisch"],
"gruppen": [
{
"gegner": "enemy.aschekraehe",
"anzahl": 8,
"startTick": 0,
"abstandTicks": 24,
"pfad": "path.enge-luft"
},
{
"gegner": "enemy.zunder-schwarm",
"anzahl": 24,
"startTick": 0,
"abstandTicks": 10,
"pfad": "path.enge-boden"
}
]
},
{
"id": "wave.enge-08",
"_note": "Eskalation: der Frostbrand-Zelot zwingt zum Stilllegen der Frostwacht oder zur Fusion Nagelschuss - und gleichzeitig steht Ruestung auf dem Brett.",
"brett": "board.die-enge",
"nummer": 8,
"prueft": "tempo",
"pauseNach": ["draft"],
"gruppen": [
{
"gegner": "enemy.frostbrand-zelot",
"anzahl": 3,
"startTick": 0,
"abstandTicks": 90,
"pfad": "path.enge-boden"
},
{
"gegner": "enemy.wallbrecher",
"anzahl": 2,
"startTick": 60,
"abstandTicks": 150,
"pfad": "path.enge-boden"
}
]
},
{
"id": "wave.enge-09",
"_note": "Bruchwelle: der Typ, der die bisherige Hauptantwort kontert. Wer auf Elementarschaden gesetzt hat, laeuft hier in Bann plus Verlangsamungsimmunitaet plus Heilung. Orakel danach.",
"brett": "board.die-enge",
"nummer": 9,
"prueft": "gemischt",
"pauseNach": ["orakel"],
"gruppen": [
{
"gegner": "enemy.banntraeger",
"anzahl": 2,
"startTick": 0,
"abstandTicks": 120,
"pfad": "path.enge-boden"
},
{
"gegner": "enemy.frostbrand-zelot",
"anzahl": 3,
"startTick": 60,
"abstandTicks": 90,
"pfad": "path.enge-boden"
},
{
"gegner": "enemy.schlundgeist",
"anzahl": 2,
"startTick": 150,
"abstandTicks": 120,
"pfad": "path.enge-boden"
}
]
},
{
"id": "wave.enge-10",
"_note": "Doppelraetsel: Luft und Ruestung gleichzeitig - zwei Antworten, die sich raeumlich widersprechen.",
"brett": "board.die-enge",
"nummer": 10,
"prueft": "luft",
"pauseNach": ["draft"],
"gruppen": [
{
"gegner": "enemy.aschekraehe",
"anzahl": 8,
"startTick": 0,
"abstandTicks": 20,
"pfad": "path.enge-luft"
},
{
"gegner": "enemy.wallbrecher",
"anzahl": 3,
"startTick": 30,
"abstandTicks": 150,
"pfad": "path.enge-boden"
},
{
"gegner": "enemy.zunder-schwarm",
"anzahl": 28,
"startTick": 0,
"abstandTicks": 9,
"pfad": "path.enge-boden"
}
]
},
{
"id": "wave.enge-11",
"_note": "Zweites Doppelraetsel: Heilung hinter Bann, dazu Tempo. Beutetisch 3 - der letzte, danach faellt Beute nur noch nach der Schlacht.",
"brett": "board.die-enge",
"nummer": 11,
"prueft": "heilung",
"pauseNach": ["beutetisch"],
"gruppen": [
{
"gegner": "enemy.schlundgeist",
"anzahl": 3,
"startTick": 0,
"abstandTicks": 120,
"pfad": "path.enge-boden"
},
{
"gegner": "enemy.frostbrand-zelot",
"anzahl": 4,
"startTick": 30,
"abstandTicks": 60,
"pfad": "path.enge-boden"
},
{
"gegner": "enemy.banntraeger",
"anzahl": 2,
"startTick": 90,
"abstandTicks": 150,
"pfad": "path.enge-boden"
}
]
},
{
"id": "wave.enge-12",
"_note": "Abschlusswelle. Im Kanon steht hier der Boss \`Der Verzehrer\` - er ist der 9. Gegnertyp und kommt mit AP-1.10. Bis dahin ist die zwoelfte Welle eine Elitewelle, die alle fuenf Raetsel gleichzeitig auf das Brett legt: Ruestung, Bann, Heilung, Tempo, Luft. Sie ist damit dieselbe Pruefung wie der Boss - drei Antworten gleichzeitig -, nur ohne Phasen.",
"brett": "board.die-enge",
"nummer": 12,
"prueft": "gemischt",
"pauseNach": ["draft"],
"gruppen": [
{
"gegner": "enemy.zunder-schwarm",
"anzahl": 36,
"startTick": 0,
"abstandTicks": 7,
"pfad": "path.enge-boden"
},
{
"gegner": "enemy.wallbrecher",
"anzahl": 4,
"startTick": 0,
"abstandTicks": 120,
"pfad": "path.enge-boden"
},
{
"gegner": "enemy.aschekraehe",
"anzahl": 8,
"startTick": 30,
"abstandTicks": 24,
"pfad": "path.enge-luft"
},
{
"gegner": "enemy.frostbrand-zelot",
"anzahl": 4,
"startTick": 60,
"abstandTicks": 90,
"pfad": "path.enge-boden"
},
{
"gegner": "enemy.banntraeger",
"anzahl": 3,
"startTick": 90,
"abstandTicks": 150,
"pfad": "path.enge-boden"
}
]
}
]
`,nn=JSON.parse(Gt),Wt=JSON.parse(_t),tn=JSON.parse(Ht),Ot=JSON.parse(xt),Ut=JSON.parse(Kt),Ae=1e3,X=e=>e/Ae;function jt(){return tn.find(n=>n.id==="curve.kopfgeld")?.kurve.values??[]}function Ct(){const e=tn.find(n=>n.id==="curve.wellen-hp");return!e||e.kurve.kind!=="geometric"?[]:Nt(e.schritte,e.kurve.base??1e3,e.kurve.factorPermille??1e3)}function Zt(){return{brett:nn[0],tuerme:Ot,gegner:Wt,wellen:Ut,kopfgeldJeWelle:jt(),hpFaktorJeWelle:Ct()}}const Vt={[I.bau]:he.bauphase,[I.welle]:he.welle,[I.sieg]:he.ende,[I.niederlage]:he.ende};function Jt(e){const n=Bt(nn),t=Zt(),i=$t({content:t}),s=new Map;t.tuerme.forEach((m,p)=>s.set(m.id,p));const a=m=>t.tuerme[m]?.id??null,o=zn(Y);let f=1,u=!1,h=i.kernpunkte;function M(){const m=o.schreiben();m.tick=i.tick;const p=i.gegnerListe(),E=Math.min(p.length,Y.gegner);for(let w=0;w{const E=n.bauplaetze[p.slot],d=E?m.find(S=>S.x===E.position.x*Ae&&S.y===E.position.y*Ae):void 0,z=p.frei?p.optionen.map(S=>({aktion:"bauen",turm:a(S.turm),kosten:S.kosten,erloes:0,erlaubt:S.erlaubt,hindernis:S.erlaubt?L.keines:L.gold})):[{aktion:"aufwerten",turm:d?a(d.art):null,kosten:p.optionen[d?.art??0]?.kosten??0,erloes:0,erlaubt:(p.optionen[d?.art??0]?.erlaubt??!1)||!1,hindernis:L.gold},{aktion:"verkaufen",turm:d?a(d.art):null,kosten:0,erloes:0,erlaubt:!0,hindernis:L.keines}];return{slot:p.slot,sorte:E?.sorte??"standard",turmId:d?.id??null,turm:d?a(d.art):null,stufe:d?d.stufe+1:0,zielwahl:d?d.zielwahl:null,bindeglieder:[],angebote:z}})},absicht(m){if(m.intent===A.pause){u=m.payload.pausiert??!u;return}if(m.intent===A.zeitraffer){f=m.payload.stufe??1;return}if(m.intent==="zielwahl"){i.absicht("zielwahl",m.payload);return}if(m.intent===A.bauen){const p=m.payload,E=s.get(p.turm);if(E===void 0)return;i.absicht(A.bauen,{slot:p.slot,turm:E});return}i.absicht(m.intent,m.payload)},stateHash(){return i.hash()}}}const qt=12,Xt={[L.keines]:"",[L.gold]:"zu wenig Gold",[L.phase]:"in dieser Phase nicht",[L.belegt]:"Bauplatz belegt",[L.leer]:"Bauplatz leer",[L.kontingent]:"Bau-Kontingent erschoepft",[L.aufgebot]:"nicht im Aufgebot",[L.endstufe]:"Endstufe erreicht",[L.verbraucht]:"schon verbraucht",[L.unbekannt]:"unbekannt"};function W(e){const n=document.getElementById(e);if(n===null)throw new Error(`Bedienung: Element #${e} fehlt in index.html`);return n}function Yt(e){switch(e.aktion){case O.bauen:return`${e.turm??"?"} · ${e.kosten}g`;case O.aufwerten:return`aufwerten · ${e.kosten}g`;case O.verkaufen:return`verkaufen · +${e.erloes}g`;case O.stilllegen:return"stilllegen";case O.aktivieren:return"aktivieren"}}function Qt(e,n){switch(n.aktion){case O.bauen:return n.turm===null?null:{intent:A.bauen,payload:{slot:e,turm:n.turm}};case O.aufwerten:return{intent:A.aufwerten,payload:{slot:e}};case O.verkaufen:return{intent:A.verkaufen,payload:{slot:e}};case O.stilllegen:return{intent:A.stilllegen,payload:{slot:e,stillgelegt:!0}};case O.aktivieren:return{intent:A.stilllegen,payload:{slot:e,stillgelegt:!1}}}}function ei(e){if(e===null)return"-";const n=e.angebote.map(t=>`${t.aktion}:${t.turm??""}:${t.kosten}:${t.erloes}:${t.erlaubt?1:0}:${t.hindernis}`);return`${e.slot}|${e.turm??""}|${e.stufe}|${e.zielwahl??""}|${n.join(",")}`}function ni(e){const n=W("vitals"),t=W("debug"),i=W("bauleiste"),s=W("protokoll"),a=W("seed"),o=W("knopf-pause"),f=W("zeitraffer");let u=!1,h="";W("knopf-angriff").addEventListener("click",()=>{e.absicht({intent:A.welleStarten,payload:{}})}),W("knopf-feldruf").addEventListener("click",()=>{e.absicht({intent:A.feldruf,payload:{}})}),o.addEventListener("click",()=>{e.absicht({intent:A.pause,payload:{pausiert:!u}})}),W("knopf-neu").addEventListener("click",()=>{e.neustart(a.value)}),a.addEventListener("keydown",d=>{d.key==="Enter"&&e.neustart(a.value)});for(const d of bn){const z=document.createElement("button");z.textContent=`${d}x`,z.dataset.stufe=String(d),z.addEventListener("click",()=>{e.absicht({intent:A.zeitraffer,payload:{stufe:d}})}),f.append(z)}function M(d){u=d.zeitraffer===0,o.textContent=u?"Weiter":"Pause",n.textContent=`Gold ${d.gold} · Kern ${d.kernpunkte}/${d.kernpunkteMax} · Welle ${d.welle}/${d.wellenGesamt} · ${d.phase} · Gegner ${d.gegnerAktiv} · Fortschritt ${d.wellenFortschritt}‰`+(d.feldrufBereit?" · Feldruf bereit":"");for(const z of Array.from(f.querySelectorAll("button")))z.classList.toggle("aktiv",z.dataset.stufe===String(d.zeitraffer))}function m(d,z,S){t.textContent=`hash ${d} · ${z.toFixed(0)} fps · ${S.toFixed(0)} tps`}function p(d){const z=ei(d);if(z===h)return;if(h=z,i.replaceChildren(),d===null){const y=document.createElement("span");y.className="hinweis",y.textContent="Bauplatz anklicken",i.append(y);return}const S=document.createElement("span");if(S.className="hinweis",S.textContent=`Slot ${d.slot} (${d.sorte})`+(d.turm===null?" · frei":` · ${d.turm} St.${d.stufe}`),i.append(S),d.zielwahl!==null){const y=document.createElement("span");y.className="hinweis",y.textContent="zielt auf",i.append(y);for(const[w,T,H]of[[ge.erster,"⇥","Vorderster — am weitesten auf dem Weg"],[ge.naechster,"◎","Nächster — kürzeste Entfernung zum Turm"],[ge.entferntester,"⇤","Entferntester — am wenigsten weit"],[ge.staerkster,"❤","Stärkster — höchste verbliebene Lebenspunkte"]]){const D=document.createElement("button");D.textContent=T,D.title=H,D.disabled=d.zielwahl===w,d.zielwahl===w&&(D.title=`${H} (gewählt)`),D.addEventListener("click",()=>{e.absicht({intent:A.zielwahl,payload:{slot:d.slot,modus:w}})}),i.append(D)}}for(const y of d.angebote){const w=document.createElement("button");w.textContent=Yt(y),w.disabled=!y.erlaubt,y.erlaubt||(w.title=Xt[y.hindernis]),w.addEventListener("click",()=>{const T=Qt(d.slot,y);T!==null&&e.absicht(T)}),i.append(w)}}function E(d){for(const z of d){const S=document.createElement("li");S.textContent=`t${z.tick} ${z.art} ${JSON.stringify(z.nutzlast)}`,z.art==="intent-abgelehnt"&&(S.className="abgelehnt"),s.prepend(S)}for(;s.childElementCount>qt;)s.lastElementChild?.remove()}return{zeigeVitals:M,zeigeDebug:m,zeigeBauplatz:p,protokolliere:E,seed:()=>a.value}}function ti(e){let n=e>>>0;return()=>{n=n+1831565813>>>0;let t=n;return t=Math.imul(t^t>>>15,t|1),t^=t+Math.imul(t^t>>>7,t|61),((t^t>>>14)>>>0)/4294967296}}const Ce=1e3/30,ii=250;function ri(e){let n=!0,t=performance.now(),i=0,s=60,a=0,o=t,f=0;function u(h){if(!n)return;const M=Math.max(1,h-t);for(t=h,s=s*.9+1e3/M*.1,i=Math.min(i+M,ii);i>=Ce;){i-=Ce;const p=e.tempo();p>0&&(e.schritt(p),a+=p)}const m=h-o;m>=500&&(f=a*1e3/m,a=0,o=h),e.bild(s,f),requestAnimationFrame(u)}return requestAnimationFrame(u),()=>{n=!1}}const li=4096,Ze=["#e6b422","#4fa3ff","#7ed957","#ff6b6b","#c792ea","#ffa94d","#4dd0c1","#f06292"];function Ie(e){return Ze[e%Ze.length]??"#ffffff"}function rn(e){return e/li*Math.PI*2}function ee(e,n,t,i){e.beginPath(),e.arc(n,t,i,0,Math.PI*2)}function ai(e,n,t,i){e.save(),e.strokeStyle="#1d232e",e.lineWidth=1;for(let o=0;o<=t.spalten;o+=1)e.beginPath(),e.moveTo(B(n,o),G(n,0)),e.lineTo(B(n,o),G(n,t.reihen)),e.stroke();for(let o=0;o<=t.reihen;o+=1)e.beginPath(),e.moveTo(B(n,0),G(n,o)),e.lineTo(B(n,t.spalten),G(n,o)),e.stroke();e.strokeStyle="#2f3a4d",e.setLineDash([4,4]),e.lineWidth=1;for(const o of t.bindeglieder){const f=t.bauplaetze[o[0]],u=t.bauplaetze[o[1]];f===void 0||u===void 0||(e.beginPath(),e.moveTo(B(n,f.position.x),G(n,f.position.y)),e.lineTo(B(n,u.position.x),G(n,u.position.y)),e.stroke())}e.setLineDash([]);for(const o of t.pfade)e.strokeStyle=o.art==="luft"?"#3a4f6b":"#5a6a4a",e.lineWidth=o.art==="luft"?2:Math.max(3,n.skala*.5),e.lineJoin="round",e.lineCap="round",o.art==="luft"&&e.setLineDash([10,8]),e.beginPath(),o.wegpunkte.forEach((f,u)=>{const h=B(n,f.x),M=G(n,f.y);u===0?e.moveTo(h,M):e.lineTo(h,M)}),e.stroke(),e.setLineDash([]);e.fillStyle="#8fbf5a";for(const o of t.eingaenge)ee(e,B(n,o.x),G(n,o.y),Math.max(4,n.skala*.22)),e.fill();const s=n.skala*1.4,a=n.skala*.5;e.fillStyle="#c8443c",e.fillRect(B(n,t.tor.x)-s/2,G(n,t.tor.y)-a,s,a),e.fillStyle="#0b0e13",e.font="10px monospace",e.textAlign="center",e.fillText("KERN",B(n,t.tor.x),G(n,t.tor.y)-a/4);for(const o of t.bauplaetze){const f=B(n,o.position.x),u=G(n,o.position.y),h=n.skala*.4;e.lineWidth=o.slot===i?3:1.5,e.strokeStyle=o.slot===i?"#ffffff":o.sorte==="hoehensockel"?"#8f7dd6":o.sorte==="grabensteg"?"#5aa9bf":"#6f7a8a",e.fillStyle="#141922",ee(e,f,u,h),e.fill(),e.stroke(),e.fillStyle="#7d8798",e.font="10px monospace",e.textAlign="center",e.textBaseline="middle",e.fillText(String(o.slot),f,u+h+8)}e.restore()}function si(e,n,t,i){const s=t.tuerme;e.save(),e.textAlign="center",e.textBaseline="middle";for(let a=0;a fehlt in index.html');const t=n,i=t.getContext("2d");if(i===null)throw new Error("haupt: kein 2D-Kontext");const s=i;let a=e(fi),o=a.brett(),f=null,u=ae;const h=ti(24301),M=ni({absicht:d=>{a.absicht(d)},neustart:d=>{a=e(d),o=a.brett(),f=null,u=ae}});function m(){return Sn(o,t.clientWidth,t.clientHeight)}t.addEventListener("click",d=>{const z=t.getBoundingClientRect(),S=m(),y=Tn(S,d.clientX-z.left),w=En(S,d.clientY-z.top);let T=null,H=di;for(const D of o.bauplaetze){const ue=Math.hypot(D.position.x-y,D.position.y-w);ue{if(!(document.activeElement instanceof HTMLInputElement))switch(d.key){case" ":d.preventDefault(),a.absicht({intent:A.pause,payload:{pausiert:a.vitals().zeitraffer!==0}});break;case"Enter":a.absicht({intent:A.welleStarten,payload:{}});break;case"f":a.absicht({intent:A.feldruf,payload:{}});break;case"1":a.absicht({intent:A.zeitraffer,payload:{stufe:1}});break;case"2":a.absicht({intent:A.zeitraffer,payload:{stufe:2}});break;case"4":a.absicht({intent:A.zeitraffer,payload:{stufe:4}});break;case"Escape":f=null,u=ae;break}});function p(){const d=window.devicePixelRatio>0?window.devicePixelRatio:1,z=Math.round(t.clientWidth*d),S=Math.round(t.clientHeight*d);t.width!==z&&(t.width=z),t.height!==S&&(t.height=S),s.setTransform(d,0,0,d,0,0)}function E(){return f===null?null:a.bauplaetze().find(d=>d.slot===f)??null}ri({tempo:()=>a.vitals().zeitraffer,schritt:d=>{a.schritt(d)},bild:(d,z)=>{p();const S=m(),y=a.snapshot();s.fillStyle="#0b0e13",s.fillRect(0,0,S.breite,S.hoehe),ai(s,S,o,f),ci(s,S,o,f),oi(s,S,y),si(s,S,y,h),ui(s,S,y),M.protokolliere(a.ereignisse()),u+=1e3/Math.max(d,1),u>=ae&&(u=0,M.zeigeVitals(a.vitals()),M.zeigeDebug(a.stateHash(),d,z),M.zeigeBauplatz(E()))}})}try{hi(Jt)}catch(e){const n=document.createElement("pre");throw n.className="absturz",n.textContent=e instanceof Error?`${e.name}: ${e.message}`:"Absturz",document.body.prepend(n),e}