— Hall of Fame —
Die größten Helden Edahniens
| Rang | Held | Stufe | Drachentode |
|---|---|---|---|
| Noch kein Held hat den Drachen bezwungen. | |||
| Rang | Held | Stufe | Drachentode |
|---|---|---|---|
| Noch kein Held hat den Drachen bezwungen. | |||
require/MasterBuilder.php)combat.php)fangen.php)$wizBackUrl = 'combat.php' → „Abbrechen" führt zurück in den (noch aktiven) Kampf. combat.php ist#wiz-back-btn) aus — kein Zurück mehr.fangen.php)require/Companion.php, combat.php, fangen.php)fanggeschirr (Name „Fanggeschirr", generisch/kaufbar) — darauf war das Companion-Phase-B-Fangsystem verdrahtet.jaeger_ausruestung (Name „Fangausrüstung", Quest-Belohnung aus alm_t8b) — das hatte der Spieler.fangen.php, fangen_ajax.php)seed_milestone_erstes_tier_20260531.php: Milestone-Definition erstes_tier_gefangenMilestone::award($character, 'erstes_tier_gefangen') — gibt nur beim#fang-overlay (self-contained, da fangen.php$session überschrieb die globale Session-Referenz (fangen.php, fangen_ajax.php, ajax/combat_ajax.php)combat.php + Seed)alm_t8b_stage_spur1 → succeed_flag alm_t8b_stage_spur1 (Aufwärmer 1)spur1, kein bergluchs_lauert → succeed_flag bergluchs_lauert (Aufwärmer 2 → Boss erscheint)bergluchs_lauert gesetzt → Boss-Etappe (forced encounter + no_kill, Fang setzt bergluchs_riese_gefangen)hagen_streicher/almhaenge: EIN Einstieg „Los. Auf die Jagd." → navigiert auf die neutralenpc_hagen/kampf_sieg: EIN Sieg-Bark-Node → navigate_js: combatResumeFromDialog() → schließtnavigate_js + effects ohne terminal:true — Effects wurden nie gesendet (dlg-system.js)combat.php)?succeed_flag= — Quest-Stage-Flags erst NACH dem Sieg setzen (combat.php, CombatSession, CombatService)combat.php liest ?succeed_flag=<key> (Whitelist [a-z0-9_], max 80) und legt ihn auf $creature.CombatSession::start persistiert ihn in der enemies-JSON (wie no_kill/meisterkampf).CombatService::applyWin setzt nach gewonnenem Kampf alle succeed_flags der Gegner idempotent increature_definitions.no_kill_flag)creature_definitions.no_kill_flag VARCHAR(100). Ist gesetzt + der Spieler hat dieses$creature['no_kill'] aus no_kill_flag + Spieler-Flag ab (vor Session-Start);no_kill-Marker in der enemies-JSON (wie meisterkampf).$enemyHpFloor = no_kill ? 1 : 0, durchgereicht in alle dreiCOMBAT_STATE.creature.no_kill. Da die HP auf 1 fälltseed_bergluchs_no_kill_20260531.php setzt bergluchs_riese.no_kill_flag = 'bergluchs_lauert'dlg-system.js)navigate_js in autoTerminal() (dlg-system.js)$t4Done prüfte toten Quest-Slug alm_t4_kellerproblem (almhausen.php)terminal: true — alm_t8b komplett überzogen (seed_fix_alm_t8b_effect_terminal_20260531.php)dorfplatz.annehmen hatte effects + choices: [[Gehen]] → Effects liefen nie → Quest wurde nie gegeben → Pre-Quest-Cond am Dorfplatz matched weiter → Hagen blieb stehen.almhaenge.etappe3_falle hatte effects: set_flag bergluchs_lauert + navigate ohne terminal → Forced-Encounter feuerte nie.dorfplatz.nach_sieg hatte complete_quest + give_quest:t9 + navigate ohne terminal → Quest-Abschluss lief nicht.sieg_etappe1_done, sieg_etappe2_done) hatten effects + navigate_js ohne terminal → Stage-Flags wurden nie gesetzt.dorfplatz.annehmen: effects entfernt (verschoben zu annehmen_gehen)dorfplatz.annehmen_gehen: + effects (give_quest + set_flag jagd_aktiv) + terminal:truedorfplatz.nach_sieg: + terminal:truealmhaenge.etappe1_spur / etappe2_pranken / etappe3_falle / etappe3_falle_nochmal / abschied_zum_dorf: + terminal:truekampf_sieg-Sub-Nodes (sieg_etappe1_done, sieg_etappe2_done, sieg_bergluchs_getoetet, sieg_generic): + terminal:truekampf_start + kampf_hp50 Sub-Nodes: + terminal:true (für Konsistenz + sicheren autoTerminal-Close)combat.php, fangen.php, templates/css/combat.css)seed_npc_hagen_kampf_hp50_20260531.php)seed_npc_hagen_kampf_start_20260531.php)seed_fix_alm_t8b_kampfsieg_flags_20260531.php)tools/gen_image.py — Bildgenerierung über Gemini API (Nano Banana)seed_fix_npc_hagen_trigger_etappe_20260531.php)companion_definitions.trigger_flag + blocked_flag.Companion::triggeredNpcAlly($flags) (neu): liefert den NPC-Slug, dessen trigger_flag gesetzt + blocked_flag nicht gesetzt ist.combat.php: NPC-Ally = &ally=-Param ODER getriggert → begleitet in jedem Kampf, solange das Flag steht. npc_hagen an bergluchs_lauert / bergluchs_riese_gefangen gekoppelt (gleiches Flag wie der Bergluchs-Forced-Encounter — Hagen + Boss erscheinen zusammen, beide enden beim Fang).companion_definitions.trigger_location (neu) — ein flag-getriggerter NPC begleitet NUR im passenden Wald-Ort. Hagen → almberg_almhausen: in anderen Wäldern/Dungeons greift er NICHT (dort kommt der eigene Begleiter). triggeredNpcAlly($flags, $location) prüft Flag + Ort; combat.php übergibt den aktuellen $__locationSlug (Dungeon/Quest = '' → kein ortsgebundener NPC).seed_fix_npc_hagen_trigger_flag_20260531.php (Flag + Stopp-Flag + Ort).bergluchs_lauert setzen (wenn Hagen mitkommt / Jagd beginnt); endet automatisch via bergluchs_riese_gefangen (setzt fangen_ajax). Spieler jagt an den Almhängen → Bergluchs + Hagen erscheinen zusammen.almhausen.php)$hagenPre (T-8 completed, T-8b wederop=almberg): DialogSystem::render('hagen_streicher', 'almhaenge', $character)seed_fix_marta_fangausruestung_navigate_20260531.php)seed_fix_alm_t8b_hagen_almhaenge_split_20260531.php)hagen_streicher/dorfplatz) gekürzt auf:hagen_streicher/almhaenge) neu angelegt (active=1):hagen_streicher/dorfplatz NUR wenn:almhausen.php?op=almberg): render hagen_streicher/almhaenge wenn quest_active alm_t8b_jaeger + has_item jaeger_ausruestung + flag_not bergluchs_riese_gefangen.seed_fix_alm_t8b_jaeger_fangausruestung_20260531.php)item_definitions.jaeger_ausruestung.name: „Hagens Spezialausrüstung" → „Fangausrüstung" + Description präzisiert (Beruhigungsmittel ergänzt)quest_definitions.alm_t8b_jaeger: Description + Objective-1-Label auf „Fangausrüstung"quest_angebot Z.3 nennt das Ding explizit „Fangausrüstung" (Aufzählung bleibt erklärend dahinter: „— Köder, Wurfnetz, Handschuhe, Beruhigungsmittel, alles drin"); annehmen + warte_ohne_item + Stage-1-Choice umtextethagen_streicher mit Portrait (seed_npc_hagen_portrait_20260531.php)npc_slug = hagen_streichername = „Hagen Streicher"location_slug = almhausen_dorfplatzavatar_path = characters/npcs/hagen_portrait.webpai_prompt ausgeschrieben (Jäger Anfang 50, sehnig, fängt lebend statt zu töten, kurz-direkt, trocken-ironisch) — bereit für KI-Chat sobald das Free-Chat-System live istalm_t8b_jaeger — 3-Etappen-Jagd mit NPC-Begleiter Hagen (seed_fix_alm_t8b_jaeger_etappen_20260531.php)require/MasterBuilder.php)GAMEDATA_TABLES: creatures (tot) raus, die 7 echten Content-Tabellen rein. gamedata.sql dumpt jetzt 21 statt 14 Tabellen.SKIP_DATA_TABLES: combat_sessions, character_dungeon_runs, character_dungeon_clears ergänzt (Runtime/User — explizit ausgeschlossen, kein Content).raeuberhauptmann.special_loot_pool gesetzt, klara_frost-NPC da.alm_zimmermiete_schulden.category ''→'side' auf Live + lokal.todos/quest_zimmermiete_category_leer.md — Quest-KI: Korrektur-Seed für die ''-category (Quelle in archivierten Seeds, eingefroren).admin_patch.php)on_start → kurz nach Kampfbeginnenemy_hp_below / player_low / ally_low → nach der Runde, wenn HP-% die Schwelle unterschreitet (1×)on_win / on_lose → VOR dem Sieg-/Niederlage-Screen (proceedOutcome als onClose deferred)almhausen.php)alm_t8_kampfkunst completed UND alm_t8b_jaeger NICHT completed.DialogSystem::render('hagen_streicher', 'dorfplatz', $character) (kein Takeover →alm_t8b_jaeger — 3-Etappen-Jagd mit NPC-Begleiter Hagen (seed_fix_alm_t8b_jaeger_etappen_20260531.php)alm_t8b_jaeger nutzt bestehende Kreatur berg_luchs~~ (seed_fix_alm_t8b_jaeger_berg_luchs_20260531.php — SUPERSEDED)quest_definitions.alm_t8b_jaeger.objectives[1].flag: bergluchs_riese_gefangen → alm_t8b_bergluchs_gefangen (quest-spezifischer Flagname — verhindert dass ein vor-Quest-Fang die Quest sofort als erfüllt markiert)quest_angebot + alle Lines: „Riesen-Bergluchs"/„mannsgroß" → „alt"/„narbig"/„schwer wie ein Mann"almhaenge_los.navigate: combat.php?type=quest&slug=bergluchs_riese → combat.php?type=quest&slug=berg_luchsalm_t8b_jaeger — Fang-Quest statt Kill-Quest (seed_fix_alm_t8b_jaeger_fangen_20260531.php)kill: bergluchs_riese × 1 → flag: bergluchs_riese_gefangenquest_angebot Lines: „Lebend brauch ich ihn." — Köder, Wurfnetz, Beruhigungsmittel statt Schwertalmhaenge_los navigate bleibt auf combat.php?type=quest&slug=bergluchs_riese — fangen.php (Phase B) erfordert eine aktive combat_session + Token-Abgleich. Spieler kämpft → klickt im Kampf-Dock auf „Fangen" → Minispiel.nach_sieg Lines: „gefangen" statt „erlegt"; Hinweis dass das Tier dressierbar ist (Tutorial-Beat fürs Companion-System)intro-Choice-Texte: „Bergluchs ist tot" → „ist gefangen", „Auf zum Bergluchs" → „Stellen wir ihm die Falle"bergluchs_riese-Kreatur braucht is_tameable=1 + verknüpfte companion_def_id (neue Definition für Riesen-Bergluchs als Begleiter)fangen_ajax.php op=resolve muss bei erfolgreichem Fang von bergluchs_riese das Flag bergluchs_riese_gefangen in den prefs setzen — sonst kann die Quest nicht abschließenjaeger_ausruestung bleibt narratives Quest-Item ohne eigene Mechanik (der Fang läuft über Companion Phase B mit normalem fanggeschirr aus der Ausrüstung)alm_t8b_jaeger — Hagen Streicher und der Riesen-Bergluchs (seed_quest_alm_t8b_jaeger_20260531.php)alm_t9_ernte.prerequisite: alm_t8_kampfkunst → alm_t8b_jaegert8_nach_sieg: give_quest:alm_t9_ernte → give_quest:alm_t8b_jaegerhelga_t9 Annahme-Cond: quest_done auf alm_t8b_jaegernach_sieg gibt T-9 nach Bossfight-Siegcreature_spawn_locations.forced (TINYINT) — markiert eine Pflicht-Begegnung.Creature::forcedEncounter($loc, $phase, $context) (neu): liefert die Kreatur garantiert (statt Zufall), solange required_flag im Spieler-prefs['flags'] gesetzt + blocked_flag nicht gesetzt ist; höchstes spawn_weight = Priorität. Nutzt die bestehenden night/day/weather-Filter.combat.php (normaler Waldkampf): übergibt jetzt die Spieler-Flags als Spawn-Context (vorher leer → flag-gegatete Spawns griffen gar nicht) und prüft forcedEncounter vor dem Zufalls-Spawn. Getriggerte Kreatur skaliert auf max(Basis, Spielerlevel).blocked_flag (z. B. <slug>_gefangen, das fangen_ajax ohnehin setzt) stoppt ihn.seed_forced_encounter_bergluchs_20260531.php): bergluchs_riese @ almberg_almhausen, Flag bergluchs_lauert. Getestet (ohne Flag → kein forced; mit Flag → garantiert; + gefangen → gestoppt).docs/forced_encounters.md (für Quest-KI).combat_sessions.ally (JSON) + companion_definitions.is_npc_ally + ability_unlock_level.start($ally), ally(), updateAlly(), Persistenz). clear() schreibt bei source=companion HP/Ohnmacht zurück + zählt den Kampf (Training); npc transient.allyFromCompanion / allyFromNpc, unlockedAbility (Freischaltung ab ability_unlock_level), npcDefinitionBySlug, firstAbility.ally_ability-Flag), NPC autonom (KI, sobald Cooldown bereit). 4 Effekt-Typen strike/dot/buff (via Buff-System)/guard (Selbstheilung) + Cooldown.?ally=<slug> NPC pausiert eigenen Begleiter, sonst aktiver Begleiter) → Session. Begleiter-Panel (Portrait + Live-HP-Balken + Fähigkeits-Button bei Freischaltung), ally in COMBAT_STATE, updateAllyBar + combatAllyAbility(). combat_ajax.php reicht ally_ability durch.seed_companion_phase_c_content_20260531.php): 4 Signatur-Fähigkeiten (Giftbiss/Schildhaltung/Rudelheulen/Hinterhaltssprung, unlock 5) + npc_hagen (NPC-Partner alm_t8b) + begleiter_bergluchs + bergluchs_riese (geklont aus berg_luchs, is_tameable, verknüpft).&ally=npc_hagen (Quest/Dialog-KI), Phase D Stall-UI, Begleiter-Portraits (Grafik-KI).is_tameable-Gegnern + „Fangen"-Button im Dock (nur bei fangbarem Gegner, HP-unabhängig). tameable + hasFanggeschirr in COMBAT_STATE, combatTame()-JS (warnt ohne Fanggeschirr, sonst → fangen.php). Meisterkampf ausgenommen.tame_difficulty, schrumpft pro Runde) + Token, legt beides in prefs['fang'] ab, rendert den Balken (rAF-Marker, Stopp per Button/Space). Inline-CSS (Jade-Skin).Companion::catch, halbe EP, kein Loot, Session beendet. Stall-voll-Abfang.fangen.php + fangen_ajax.php in navExempt (Minispiel ohne Nav-Flow + AJAX)..tameable-Chip + .ca-tame-Button-Styling.max_stack 1→höher (Nachtrag im Todo). Nächste: Phase C (Begleiter kämpft mit) + D (Stall & Training).companion_definitions (Art-Template: Basis-Stats, Wachstum, trainability, level_cap, available_actions) + character_companions (Instanz: level, current_hp, status active/unconscious/stabled/resting, catch_grade 0/1/2, days_progress, interaction_count, kills). Pattern wie char-Tabellen (character_id int unsigned, Indizes statt harter FKs).require/Companion.php — gesamtes SQL gekapselt: catch / forCharacter / active / get / count / setActive / stable / rename / release / setHp / recordFight / checkLevelUp / onNewday / stats / computeMaxHp. Fang-Güte-Bonus (CATCH_GRADE_BONUS: 4/5 → +1/+1, 5/5 → +2/+2 +5%HP). Leveling über Interaktionen ODER Tage bis level_cap. Ohnmacht statt Tod, Newday-Erholung.seed_companion_definitions_phaseA_20260531.php: 4 Definitionen (Waldspinne/Nacht-Faultier/Grauer Wolf/Schleichkatze, archetyp-basiert tariert) + creature.companion_def_id+is_tameable der 4 Tiere gesetzt. Idempotent.fangen.php) + Badge, C Begleiter kämpft mit, D Stall & Training.core/CombatService::applyWin markiert Items aus Loot::rollAndGive (Special-Pool) mit special => true + quality_label (aus Item::QUALITY_LABELS).combat.php Siegbildschirm: Special-Items bekommen ✨-Glyph + Klasse combat-loot-special iq-{quality}, Name in .cls-shine, darunter das Seltenheits-Label (Fein/Makellos/Meisterhaft/Legendär).templates/css/combat.css: Tier-Farb-Variablen je Qualität (gespiegelt aus inventory.css --rar-*: fine=grün, superior=blau, masterwork=lila, legendary=gold) treiben Shine-Gradient + Glow + Label + Zeilen-Hintergrund. .cls-shine = animierter Gradient-Text (Shine-Sweep) + drop-shadow-Glow in Tier-Farbe, ✨ pulsiert. prefers-reduced-motion respektiert.Creature::dynamicStatMult bekommt einen creature_gear_mode-Schalter (absolute|fraction|hump). Im hump-Modus skaliert der Monster-Aufschlag mit dem Gear-Fortschritt des Spielers — level-invariant über gearPoints / (baseAtkDef + 4) (0=nackt · ~0.5=halb · ~1=voll; baseAtkDef=2·Level liefert das Level). Monster steigen bis zur Schwelle an (fordert halb), werden darüber abgefedert (entlastet voll → keine Inversion). Nackt (0 Gear-Punkte) bleibt unberührt.absolute (gated), der Settings-Seed schaltet auf hump.Creature::dynamicStatMult($effAtkDef, $baseAtkDef) — creature_stat_mult (global, default 1.0) × Gear-Aufschlag 1 + Gear-Punkte · creature_gear_scaling, gedeckelt durch creature_gear_cap. Gear-Punkte = absolute att+def aus Ausrüstung (eff − base). Nackt (0 Punkte) → ×1.0. (Erst per Verhältnis eff/base versucht — explodiert bei niedrigem Level, ließ halb+voll in den Cap laufen → halb schlechter als voll + Tanks bei L1 für alle unschlagbar. Absolute Punkte sind monoton: früh klein, wachsen mit Level.)Creature::applyStatMult($creature, $mult) — skaliert hp/attack/defense (+ _hp_max).combat.php: nach Kreatur-Finalisierung (vor Anzeige + CombatSession::start), für alle außer Meisterkampf; deterministisch aus effectiveAttack()+effectiveDefence() vs attack()+defence() → auf Resume identisch.tools/combat_balance_sim.php und admin_grotte.php (op=combat_balance Heatmap): wenden denselben Multiplikator pro Gear-Profil an (Naked-Referenz) → live tunebar per Sim/Heatmap. (Heatmap hatte ihn anfangs nicht → zeigte alte Werte; nachgezogen.)seed_settings_creature_mult_20260530.php: creature_stat_mult=1.0, creature_gear_scaling=0.03, creature_gear_cap=2.0.require/CombatSim.php — portiert rollDamage / resolveEnemyAttack / Initiative /tools/combat_balance_sim.php — CLI-Report. Args: `--levels=1-15 --runs=100 --seed=42admin_grotte.php — neuer Handler op=combat_balance (Level 4): HTML-Heatmap mitcommon.php — require/CombatSim.php registriert.seed_rebalance_creatures_1zu1_20260530.php — neue Basis-hp/attack/defense pro Kreatur (Regel: att ×0.5 min 1, def ×0.45, hp ×0.5; Rollen erhalten — Tanks mehr HP/Def, Glaskanonen mehr Att). Idempotent, absolute Soll-Werte. Beispiele: Straßenräuber 16/3/1 → 8/2/0, Schattenschurke 10/5/0 → 5/3/0, Räuberhauptmann 40/6/5 → 20/3/2, Trollfürst 180/10/7 → 90/5/3.require/Creature.php: gepanzert-Trait Flat-Def-Bonus +3 → +1 (bei den neuen kleinen Def-Werten wären gepanzerte Gegner sonst unkillbar).combat_def_mitigation 0.7→0.2 ändert voll-Gear ebenfalls nicht (symmetrisch: senken lässt auch den Spieler härter treffen → killt nur schneller). Fazit: Herausforderung für Gearte kommt strukturell aus Dungeons (Mob-Pack + Boss + level_bonus), Bossen und Untergearten/Meisterkampf — nicht aus freien Einzel-Mobs. Das ist eine Folge der vom PL gesetzten 1/1+10HP-Ökonomie, kein Bug.level_bonus noch im Old-Economy-Maßstab — nach Playtest per Sim nachziehbar.seed_fix_startstats_att_def_20260530.php setzt startattack=1, startdefense=1 (idempotent).Character::create() Code-Fallback startattack/startdefense von 10 → 1.intelligence-Spalte — „int" = initiative; applyLevelUpGains() erhöht korrekt initiative (+1/Level). Keine Änderung nötig.CombatService::decrementTurns() bekommt die CombatSession und macht Früh-Return bei aktivem Dungeon-Run (dungeonRunId() > 0) → kein Runden-Abzug für Dungeon-Kämpfe (alle 6 Aufrufstellen angepasst).combat.php Dungeon-Start: nach erfolgreichem Dungeon::startRun (nur neuer Run, nicht Resume) einmalig turns - 1.Loot::previewPool($pool, $char) (neu) — filtert einen Loot-Pool nach kampfkunst_filter/min_level ohne zu würfeln und behält drop_chance (für UI).combat.php op=intro: nach den Difficulty-Karten eine „Chance auf"-Reihe. Quelle = Boss-Kreatur-special_loot_pool (+ Dungeon-Pool falls vorhanden), dedupliziert per Slug. Bildpfad: Item-img → Konvention templates/assets/equip/weapons/{slug}-sm.webp → .webp → Platzhalter „?". Tooltip zeigt Droprate.raeuberhauptmann special_loot_pool um Magier-Set (raeuber_stab + raeuber_fokus, kampfkunst_filter [6,7,8]) ergänzt — vorher gingen Elementar/Licht/Schwarzmagie leer aus (leere Vorschau). Idempotenter Soll-Pool mit 7 Items. → seed_fix_raeuberhauptmann_magier_pool_20260530.phpDungeon::resolvePhase (umgebaut): aktuelle Stufe = erste noch nicht gecleerte Phase (character_dungeon_clears, difficulty-unabhängig). Stufe 1 = Basis (phase_id ''), phases[0] = Stufe 2, … Alle durch → letzte Phase.Dungeon::allStufenCleared (neu) + canEnter: alle Stufen durch + nicht final_repeatable → „bereits vollständig abgeschlossen" (Story-Dungeon gesperrt). final_repeatable=1 → letzte Stufe bleibt farmbar (Loot-Dungeon).dungeon_definitions.final_repeatable (TINYINT, Default 0).final_repeatable=0 (Story). Verlauf getestet: 0 Clears→Stufe 1, +1→Stufe 2, +2→Stufe 3, +3→gesperrt; mit final_repeatable=1→letzte farmbar.Dungeon::currentStufe() (neu) → Intro-Badge + Scene-Label zeigen jetzt immer die Stufe (auch Basis = Stufe 1; vorher nur bei Nicht-Basis-Phasen sichtbar — durch den Wechsel quest→clear war die Badge auf Stufe 1 verschwunden). Format: nur „Stufe N" (siehe oben).level_bonus + Räuberlager auf „Basis-Monster, nur stärker"combat.php)almhausen.php?op=bach)seed_fix_helga_t9_navigate_zum_dungeon_20260529.php)quest_not_active → unsichtbar weil Quest schon active)objectives_met → unsichtbar weil Boss noch nicht tot)raeuberhauptmann (seed_fix_raeuber_quests_clear_modell_20260529.php)ernst_sack/t7c_angebot: „Ihr Anführer ist nicht der gleiche — dunkler, gepanzert" raus → neutralhelga_brandt/t9_beweis: „Räuberkönig" raus → „letzter Anführer"helga_brandt/t9_nach_sieg: „Der Räuberkönig ist gefallen" → „Der Anführer ist gefallen"seed_fix_hilde_intro_dedup_20260529.php)seed_fix_t9_auf_raeuberlager_phase3_20260529.php)alm_t9_ernte Objective: monster:'*' × 3 → kill: raeuberkoenig × 1t9_beweis: Lines auf Räuberlager-Kontext + navigate direkt zu combat.php?type=dungeon&op=intro&slug=raeuberlager_muehlenbacht9_nach_sieg: Lines „Der Räuberkönig ist gefallen…" (Tutorial-Closing-Beat erhalten)baurecht_bauerngasse + edahnien_freigeschaltet)dungeon_definitions.raeuberlager_muehlenbach.phases (Trigger quest:alm_t9_ernte,status:active, boss raeuberkoenig)strassenraeuber_meister, schattenschurke_meister, raeuberkoenigseed_fix_klara_meisterkampf_lock_20260529.php)seed_fix_klara_meisterkampf_direkt_20260529.php)CombatService::rollDamage)almhausen.php)almhausen.php?op=dorf_infoop=dorf_info: Coming-Soon-Stub (📜 + Platzhaltertext). Echte Pergament-Seitetodos/dialog_konrad_ueber_unser_dorf.md (NPC-Dialog-KI) — Choice bei KonradapplySkillEffects: regen bleibt round-start-getickt; Log macht das klar: „♥ Regeneration: +5 LP zu jedem Rundenbeginn (2 Runden)".combat.php: redundante/rohe effects_applied-Log-Zeile (〜 self_regen) entfernt — der Server-Kampflog (data.log) beschreibt alle Effekte bereits sauber.regen (Heilung pro Runde) — im Rundenbeginn-Tick verarbeitet. Status-Effekte tragen jetzt ein target ('self'|'enemy'); applySkillEffects routet entsprechend (self → addStatusEffect, enemy → addEnemyStatusEffect).blind (Fehltrefferchance X%) — in resolveEnemyAttack geprüft, neues Ergebnis 'blind' (Gegner verfehlt), in beiden Angriff-Handlern geloggt.lifesteal_pct: neues Schema-Feld (skill_definitions, ALTER in patch.sql) — heilt %-Anteil des verursachten Skill-Schadens (in doPlayerAttack).combat.php:templates/css/combat.css: Styles für .combat-mp-display, .combat-mp-bar(-wrap/-head/-fill), .combat-skill-grid/-btn (cs-name/cs-mp/cs-eff/cs-desc/cs-disabled), .combat-skill-empty.require/Item.php → BONUS_RANGES)seed_items_jaeger_quest.php, seed_item_fanggeschirr.php)jaeger_ausruestung — Quest-Item (category=document, quest_item=1, stackable=0, kaufbar=0)bergluchsfell — Trophäe (category=loot, rarity=rare, stackable=1)fanggeschirr — Verbrauchsitem (category=consumable, usable=1, stackable=1, kaufbar=1, price_gold=75)combat.php)combat.css, combat.php)combat.php $__killMode aus city/location: almhausen (oder location enthält almhausen) → 'black', sonst → 'white'. In COMBAT_STATE.killmode für JS exponiert.ckill-black/ckill-white. combat.css: .ckill-black → mix-blend-mode:screen, .ckill-white → filter:url(#crt-white-remove).killmode (schwarz→screen, weiß→white-remove).combat.php)lungePortrait/hitPortrait/flashHp/frameClass/spawnRing/spawnBgFlash (mit Null-Guards + Reflow-Restart).playRoundFx(data) leitet aus player_damage/enemy_damage ab: Spieler-Treffer (Lunge + Slash + Gegner-Shake + HP-Flash + Impact-Ring + BG-Flash), Gegner-Konter zeitversetzt (Gegner-Lunge + Spieler-Shake + HP-Flash). Aufruf in applyResult() nach dem HP-Update.combat_ajax.php: neue Aktion skill (Slug-Whitelist a-z0-9_), Slug an runRound durchgereicht.CombatService::runRound($char,$session,$action,$targetIdx,$skillSlug): Skill-Branch — serverseitige Prüfung (Besitz via Skill::forCharacter, Klassen-Eignung, MP via spendMp, Meisterkampf-Sperre), synthetische „Ability" für den Schadens-Teil (mult 0 = reiner Buff/Heil-Skill).applySkillEffects(): Heilung (Spieler), Status-Effekte auf Gegner (poison/bleed/burn via addEnemyStatusEffect), Buff auf Spieler (Buff::apply). Item-Procs nur bei Waffen-Angriffen, nicht bei Skills.CombatSession: mp() + spendMp(); buildResponse/toArray liefern mp/mp_max. Getestet e2e (Schaden/Heilung/Buff/Gift/zu-wenig-MP, korrektes „Ausdauer"-Label).CombatSession::start() füllt current_mp aus characters.current_mp (Fallback maxMp, guarded). clear() schreibt den Kampf-Stand via setCurrentMp() zurück.Character::applyLevelUpGains(): maxmp +levelup_mp und current_mp voll-Refill beim Aufstieg (wie HP).characters.current_mp-Spalte + currentMp()/setCurrentMp() von der User/Character-KI nachgezogen → persistenter Pool ist live.startmp=10, levelup_mp=5.give_skill Effekt + has_skill/has_skill_not Conditions (dialog.php, DialogSystem.php)dialog.php neuer give_skill-Case: ruft Skill::give($character, $slug) auf.DialogSystem.php choiceAllowed(): has_skill + has_skill_not nach has_item_notdocs/dialog_referenz.md: give_skill in Effekt-Tabelle, has_skill/has_skill_not in Conditions-Tabelle.CLAUDE.md: beide Tabellen ergänzt.patch.sql): skill_definitions (slug, source kunst/specialty, source_id, mp_cost, target, damage/heal/status_json/buff_json, unlock_level, flavor) + character_skills (max 4, UNIQUE char+slug).require/Skill.php: kapselt alles SQL — definitionBySlug/forSource, owned/has/count/isFull, give (given/pending/duplicate/unknown — bei vollem Satz pending in prefs parken), forget (pending rückt nach), forCharacter (owned ∩ passende kunst/specialty), resourceName (Mana|Ausdauer per specialty). Getestet: 4er-Limit, pending-Nachrücken, Klassen-Filter.seed_settings_progression.php): startmp=10, levelup_mp=5.skill-system (sk-01..sk-11).characters.maxmp — Ressourcen-Feld für Kampf-Fähigkeiten (todos/user_maxmp_ressource_feld.md)patch.sql: ADD COLUMN maxmp + ADD COLUMN current_mp — beide NOT NULL DEFAULT 10.require/Character.php: maxMp()/setMaxMp() + currentMp()/setCurrentMp() (clampt auf 0…maxMp)Character::create(): beide Felder im INSERT, Startwert = startmp Settingstartmp=10 + levelup_mp=5 bereits in seed_settings_progression.php ✓core/charstats_func_inc.php: Mana/Ausdauer-Bar zeigt currentMp / maxMp mit Prozent-Fill.templates/edahnien.css: .char-bar-track.mp (blau) + .char-bar-track.mp.ausdauer (gelb)REQUIREMENTS/MAX_STUFE/nextStufe/allDone (feste 3 Stufen, Level 5/10/18) entfernt.canChallenge() neu: prüft free_exp >= maxexperience(), level < levelCap, keine Buffs. Liefert level/next_level/free_exp/need_exp/cap/at_cap. stufe-Alias = aktuelles Level (Kompatibilität für combat.php).levelCap()/atLevelCap(): Setting level_cap (Default 10 = Phase 1), später phasenabhängig.buildOpponent($char, $stufe=0): $stufe ignoriert, Gegner skaliert rein ans Level (best-equipped-Profil).applyVictory($char, $stufe=0): Level +1 + applyLevelUpGains, Reward nur Gold (kein EXP — wäre zirkulär), Flag t8_meisterkampf_gewonnen bleibt (T-8-Quest-Nachweis).Meisterkampf::bestEquippedProfile($level) → {hp,att,def,ini} aus den Settings. Ergibt: HP = 10+(L-1)·10 · ATT = 13+(L-1)·2 · DEF = 14+(L-1)·2. buildOpponent() nutzt es (stufeMult/abstrakte Formel entfernt).Character::applyLevelUpGains($times=1) → hebt maxHP (+voll heilen), attack, defence, initiative gemäß Settings an; lastLevelUpGains() liefert die Deltas. Wiederverwendbar für künftige Level-Up-Trigger (z. B. forest-EXP).combat.php)training.php)setTimeout(go, 20000) — 20-s-HardstopsetTimeout(…) auf loadedmetadata (Video-Dauer-Timer)setTimeout(go, 3500) (kein-Video-Platzhalter-Redirect)setTimeout(go, 2500) (Autoplay-Block-Fallback)setTimeout(go, 1500) (Video-Error-Redirect)v.addEventListener("ended", go) (Auto-Redirect bei Video-Ende)training.php)training_schwert_2.mp4 gefunden (Perlös Waffe = Schwert)training_axt_2.mp4 gefundentraining_lichtmagie_2.mp4 fehlt → fällt korrekt auf Schwert-Fallback zurückseed_fix_t8_flow_konsolidieren_20260529.php)t8_ausbildung + t8_ausbildung_abschluss entfernt (toter Code)dialog.php, training.php, seed_fix_t8_ausbildung_kunststufe_20260529.php)require/Meisterkampf.php, combat.php, core/CombatService.php, require/CombatSession.php, training.php)REQUIREMENTS (3 Stufen: Level/Kampfkunst-Stufe/freie EXP) — Single Source of TruthnextStufe(), allDone(), canChallenge() (Gate-Check: Level + Kunst + EXP + keine Buffs)buildOpponent($char, $stufe) — der Lehrmeister (Krom/Klara je Stadt), skaliert mit Spieler-Level (Design-Formel HP=Lvl×12+20, ATK=Lvl×2+3, DEF=Lvl+2, +15%/Stufe). Portrait aus characters/npcs/{krom|klara}_portrait.webp.applyVictory($char, $stufe) — Level+1, meisterkampf_stufe, Flag t8_meisterkampf_gewonnen + meisterkampf_stufe_N_done, skalierter RewardcanChallenge) — direkter URL-Aufruf ohne erfüllte Voraussetzungen → Redirect zurück zum Ausbildungslagermeisterkampf-Marker (in enemies[0])CS.meisterkampf)isMeisterkampf() (Marker in enemies[0]). Bei Meisterkampf: Item-Procs aus ($procs = [])applyDeath($char, isMk=true): immer geschützt, volle Heilung, kein Verlust, outcome meisterkampf_lost (kein Cooldown — sofort nochmal)Meisterkampf::applyVictory, kein normaler Loot, outcome meisterkampf_wonseed_fix_t8_meisterkampf_level_20260529.php, require/Quest.php, training.php)core/CombatService.php, combat.php)isTutorialProtected($char) = empty(prefs['flags']['tutorial_done']).applyDeath() prüft den Schutz: wenn aktiv → kein Gold/Gems-Verlust, volle Heilung (hitpoints = maxHitpoints), Rückgabe protected=true. alive bleibt true.protected: bei Schutz outcome='lazarett' + freundliche Log-Zeile statt outcome='lose'.died (fair, Fortschritt weg), aber der Char überlebt.training.php, seed_kunst_unlock_content.php)op=skill (Ausbildungsraum): Stufe-kaufen zeigt nicht mehr nur Inline-Text, sondernop=unlock: Vollbild-Overlay (z-index 99999, schwarz) mit mp4-Video des Lehrers +seed_kunst_unlock_content.php: content_strings (training.php/de) — Titel, Weiter,todos/grafik_video_kampfkunst_unlock.md — mp4-Clips (generisch + optional pro Stadt/Lehrer).seed_fix_klara_t8_folgenodes_20260529.php)navigate: "almhausen.php?op=lager" (Klaras Ausbildungslager-Page)t8_ausbildung_abschluss zusätzlich replay: "free" — set_flag ist intern idempotent (setzt Wert auf true, zweiter Aufruf no-op), kein Replay-Schaden möglich. t8_nach_sieg ist schon free (qs-21).flag_not: t8_kampfkunst_gelernt greift)seed_fix_quest_annahme_loop_20260529.php)almhausen.php)replay: "free" (seed_fix_quest_effect_nodes_replay_free_20260529.php)Quest::accept() returnt true wenn schon active — keine Doppel-AnnahmeQuest::complete() failt wenn Status != active — keine Doppel-Rewardsrathaus/stempel_bogart, Mira anmeldung/bio_fehltrathaus_almhausen/stempelschenke_almhausen/t2_abschluss, t4_abschluss, t7b_angebot ← gemeldeter Bughelga_t9/t9_beweisklara_t8/t8_quest_annehmen, t8_nach_siegcompleted sichtbar (seed_fix_quest_abgabe_choices_20260529.php)quest_active: <slug> — Choice jetzt nur sichtbar während Quest läuftreplay: "free" — gleicher Schutz wie Ernst T-6/T-7c aus qs-15 gegen dlg_done-Replay-Deadlock bei Test-Resets. Sicher weil alle drei nur complete_quest als Effect haben (Quest::complete intern status-gegated).seed_fix_konrad_t7a_restmondblumen_20260529.php)cedCheckCompat() prüft beim Ändern jedes Dropdowns:op=stat_set: cedCheckCompat() nach jedem save() für race/kunst/specialtycedCheckCompat(int $race, int $spec, int $kunst): ?string Funktion in admin_charedi.phpcharacters.klasse entferntpatch.sql: ALTER TABLE characters DROP COLUMN IF EXISTS klasse — Spalte war Legacy, nie durch Wizard geschrieben, immer 0require/Character.php: klasse() auf return 0 gestubbt mit @deprecated-Hinweisdragon.php: toten if(klasse>0){nochange[klasse]=1} Block entfernt (war wirkungslos da klasse immer 0)admin_patch.php: Patch-Upload führt jetzt alle seed_*.php + seed.php aus dem ZIP-Root automatisch aus (alphabetisch sortiert)require/Character.php, training.php, tempel.php: boughttrain-Logik komplett entferntboughttrain war ein Tages-Trainings-Zähler der nie in der DB existierte (Getter gab immer 0 zurück, Limit war wirkungslos); Training ist ohnehin durch Runden begrenzt — der Zähler war redundanttemplates/css/combat.css).battle-nav span.nav:not(.dim):hover zur Hover-Regel ergänzt (analog a.nav).battle-nav span.nav:not(.dim) { cursor: pointer; user-select: none; } — macht aktive Span-Buttons klickbar-kenntlich:focus-visible-Outline (Tastatur)span.nav.abort parallel zu a.nav.abort (rote Abbrechen-Buttons wie „Dungeon verlassen")seed_fix_berg_loot_maxstack.php)rohes_erz → 999, bergziegenfell/wildschwein_keule/bergkraut/baerenklaue → 99stackable=1 AND max_stack<=1 → 99 (fängt denselben Seed-Vergessen-Fehler bei anderen Items ab)combat.php, require/Creature.php)adminmodule/admin_charedi.php)cedCheckCompat, Z.736 Stats-Box, Z.956 Validierungs-Panel): 7→Schwarzmagie, 8→Lichtmagie.$specToKunst (Z.965/966) war unter dem alten Mapping geschrieben → semantisch verdreht. Gegen WizardHelper::KLASS_STYLES (Quelle laut Kommentar) gegengeprüft: Paladin→schwert,lanze,lichtmagie, Nekromant→schwarzemagie,elementarmagie.combat.php)seed_fix_berg_luchs_loot.php)require/BrowserSession.php)install.php, require/InstallerBuilder.php, admin_patch.php)require/InstallerBuilder.php (neu, v2 — Wipe statt Keep)buildWipeSql() → DELETE FROM über user, characters, items, character_buffs/quests/milestones/companions, houses/house_rooms/house_keys + Runtime/Logs (chat_messages, news, security_log, sessions, anticheat_log, login_log, rate_buckets, character_change_log).user (Singular!) — MasterBuilder::SKIP_DATA_TABLES listet versehentlich users (Plural), hier korrekt; sonst blieben die Alt-Accounts stehen.install.php (neu) — Standalone Setup-Wizarddbwrapper.php + require/Settings.php → db_pconnect() (Verbindungstest).dbconnect.php (var_export, 0640).install/schema.sql → install/gamedata.sql → install/admindata.sql (Wipe) der Reihe nach aus.User::create() + Character::create() + set('superuser',4)->set('tutorial_done',1)->set('city','edahnien')->save().\\", \'', \r\n).admin_patch.php — op=build_installer (neu)manifest.json (type=installer) + install.php + install/{schema,gamedata,admindata}.sql + README_INSTALL.txt + (optional) kompletten Code.MasterBuilder::build(), admindata.sql (Wipe) aus InstallerBuilder::buildWipeSql().installerAddCode(): rekursiver Walk, schließt patches/tests/cache/logs/_legacy/.git/node_modules/install/feedback/todos + dbconnect.php + *.bak/*.zip aus (admin_patch.php bleibt drin — Frischserver braucht den Patch-Manager).allowPrefix('admin_patch.php?op=build_installer').buildWipeSql(): 14 Tabellen geleert (inkl. user Singular), 4 nicht-existente übersprungen, kein users (Plural) — 0 Fehler.require/DbErrorLogger.php, require/PhpErrorLog.php, admin_grotte.php)caller ODER ein trace-Frame aus einer _-Datei stammt (Dateiname = Teil vor erstem : im Frame-String)._-Datei referenzieren (auch Stacktrace-Frames #0 /p/_x.php(5)).#(?:^|[\s(/\])_[A-Za-z0-9._-]*\.php\b# — verlangt einen Trenner direkt vor dem _, sodass Verzeichnisse mit Unterstrich (logd_evo/, _legacy/foo.php) NICHT fälschlich getroffen werden — nur Dateinamen die selbst mit _ beginnen.admin:dblog_tmp / admin:phplog_tmp, Action clear_tmp._test_create.php, _introspect_tmp.php, _x.php(12), _diag.php; trifft NICHT village.php, _legacy\foo.php, dialog.php, logd_evo-Pfadnennung.require/Character.php, village.php)village.php:74 — $session['user']['stadt'] = 1; bei JEDEM Load → saveuser() schiebt es auf den Charakter → filterKnownColumns() verwirft es + loggt. Das ist die „konsequent immer wieder"-Quelle.Character.php Newday-Reset-Loop — setzt stadt (+ 12 weitere Nicht-Spalten-Altlasten) auf 0 → loggt 1×/Newday je Feld (seltener, leicht übersehen).Character.php LEGACY_FIELDS um die 13 Altlast-Felder erweitert → filterKnownColumns() filtert sie jetzt STUMM (gleiches Muster wie fertigkeiten aus ti-36). Das set(…,0) im Newday-Loop ist für Nicht-Spalten ohnehin ein No-op — reine Logging-Bereinigung, KEINE Verhaltensänderung.village.php — die tote $session['user']['stadt'] = 1;-Zeile entfernt (nur _legacy las stadt, aktiver Code nie). specialinc/specialmisc bleiben (sind echte Spalten).combat.php)Creature::get() für Display-Felder, Kampfwerte + laufende HP aus Session)$__dungeonInfo, Phase, Progress) aus dungeon_run_id + Dungeon::activeRun() wiederhergestelltlocation-Param aus GET übernommen (backUrl/continueUrl bleiben korrekt)CombatSession::start() übersprungen — Session unangetastet$enHpCur neu) + AP ($__apCur neu) starten auf echtem laufendem Stand statt Maximumrequire/BrowserSession.php)scenarios_functional: 21 Tests → 0 grün, 21 ⚠ errorscenarios_security: 11 Tests → 2 grün (die beiden *negativen* Smoke-Tests die einen Login-Fehler ERWARTEN), 9 ⚠ errorTestRunnerAuth unverändert intakt (kein Aufweichen des IP-Checks).SCRIPT_NAME → /logd bzw. /logd_evo).http statt https: Loopback braucht kein TLS, vermeidet Zertifikats-/SNI-Fehler in curl bei Self-Signed-Certs.settings.test_runner_base_url wird weiterhin respektiert (für abweichende Ports/Setups).seed_fix_quests_wald_zu_berg_20260529.php)t7a_angebot / t7a_abgabe: „Mondblumen" → „Bergziegenfelle" (Pergamente)t7b_angebot / t7b_abgabe: „Kleeräupchen im Wald" → „Bergkraut auf den Hängen"t9_beweis: „Drei Kämpfe im Wald" → „Drei Kämpfe auf den Almhängen"bergziegenfell ×1 + bergkraut ×1 → nach Definition-Update direkt 1/3 bzw. 1/2 ohne expliziten DB-Eingriff (Quest::checkObjectives evaluiert frisch).almhausen.php)$forestUnlocked/$forestNeeded + Wald-Link in „Vor dem Dorf" entfernt — dort steht nur noch „Die Almhänge"monster:'*' (jeder Kill) → Almhänge-Kämpfe reichenforest.php city-aware Rück-Nav bleibt drin (harmlos, falls Almhausen-Spieler den Wald je anderweitig erreicht)almhausen.php, seed_almhuette_content.php)getsetting('almberg_heal_cost_per_hp', 3)tryDeductGold) + CSRF (almhCsrf('almhuette_heilen'))almhausen_almhuette.webp → Fallback auf almhausen_almberg.*combat.php, seed_almberg_kreaturen.php)location überschreibt den Stadt-Default-Spawnpool wenn auf Whitelist (almberg_almhausen, forest_almhausen, forest_waldrand). Sonst Fallback auf Stadt-Standard.almberg_almhausen: sceneLabel = 'Almhänge · Almhausen', backUrl = almhausen.php?op=almberg.continueUrl im COMBAT_STATE — „Weiter kämpfen"-Button nach Sieg ist jetzt zone-aware (Almberg → combat.php?location=almberg_almhausen, Wald → forest.php?op=kampf). Vorher war das Ziel hart auf forest.php verdrahtet.bergziege_wild: flink,stoß → tier (Flinkheit über INI 7 abgebildet)berg_luchs: flink,ausweichen,lauern → ausweichen,tier (echtes ausweichen + INI 10 bleibt)kraehenschwarm: +fliegend,tier; wildschwein + hoehlenbaer: +tier (Zähmbarkeits-Voraussetzung Phase 3)almhausen.php?op=schenke)op=schenke: die 5 Speisekarte-Nav-Links (schenke_bestellen-Links),DialogSystem::render)op=schenke_bestellen (CSRF + atomarer Gold-Abzug +todos/dialog_hilde_bestellen.md (NPC-Dialog-KI) — Bestell-Shop inseed_almberg_kreaturen.php, seed_almberg_loot.php, combat.php)rohes_erz — uncommon → Brokk (Schmiede, fehlende Erzquelle)bergziegenfell — common → Martawildschwein_keule — common → Hildebergkraut — common → Hilde / Heilmittelbaerenklaue — rare → Händler (selten, wertvoll)$sceneLabel = 'Almhänge · Almhausen' (statt generisch Waldrand)$backUrl = 'almhausen.php?op=almberg' → nach Kampf zurück zur Zone (Weiterkämpfen-Schleife)location-Whitelist-Param war bereits von anderer KI eingebautalmhausen.php?op=almberg)op=almberg POI-Seite: Atmosphäre + Runden-Check + Kampf-Button → combat.php?location=almberg_almhauseneinmalig_riesenratte_keller ODER T-4 completed ODER SU) — dauerhaftalmhausen_almberg.webp (file_exists-Guard, Bild folgt)location-Param zukunftssicher: bis Combat-KI den almberg_almhausen-Pool + Param seedt, fällt combat.php auf den Almhausen-Standardpool zurück (Seite funktioniert sofort)almhausen.php, forest.php)forest.php$forestUnlocked: T-7a aktiv/abgeschlossen oder SU — Wald bleibt danach dauerhaft erreichbar (auch für T-9: 3 Kämpfe im Wald)$t7aStatus als Variable ergänzt (analog $t6QStatus)$forestCity = $character->city() bestimmt Zurück-Ziel: Almhausen-Spieler → almhausen.php, sonst village.phpRouter::allow() nutzen jetzt $backUrl/$backLabel{city}_wald.png mit Fallback auf edahnien_wald.pngback_almhausen (neu), back_village (bestehend)almhausen.php?op=schwarzes_brett)⚔ Läuft / ✓ Abgeschlossencontinue):voraus_hint-Anzeige entfällt (gesperrte Einträge sind jetzt unsichtbar statt nur ausgegraut)brett_leer)seed_fix_t8_prereq_konrad_hinweis.php, require/DialogSystem.php)almhausen.php?op=bach)almhausen.php, village.php, badnav.php)almhausen.php Zeile 61-62: Router::allow('admin_grotte.php') + Router::allowPrefix('admin_grotte.php')village.php Zeile 125: Router::allow("admin_patch.php")trace[] pro Log-Eintrag — bis zu 10 Stack-Frames im FormatDatabase.php, DbErrorLogger.php) werden gefiltertcaller-Feld bleibt erhalten (= trace[0]) für Backward-Compattrace werden vom Reader still ignoriert<details> collapsed) damit die Listedocs/combat_referenz.md aktualisiertreplay: "free" (seed_fix_ernst_replay_free.php)common.php)PhpErrorLog-Klasse (require/PhpErrorLog.php)tail($n) — letzte N Einträge mit Tag-Erkennung (Dialog-Effect, Quest-Reward, Quest-Deliver, CHARACTER, BADNAV, setnewday, Fatal/Warning/Notice/Deprecated)countSince($ts) — wie viele Einträge seit ZeitstempellineCount() / sizeBytes() — Stats für Bannerclear() — Log leeren via Adminop=phplog Viewer (admin_grotte.php)[Dialog-Effect] und [Quest-Reward] aus unserem letzten Logging-Patch sind sofort als gelbe Tags erkennbarrequire/Item.php, templates/css/inventory.css, templates/edahnien.css).iv-D-card-icon--img).iv-pd-slot-img)$equippedMap[slot]['img'] wird nach Aufbau der Map aus $processed nachgefüllt (Reihenfolge war kritisch: Enrichment-Loop muss NACH $equippedMap-Aufbau laufen)$slot()-Closure: Image-Modus rendert <img class="iv-pd-slot-img"> + versteckten <span class="iv-pd-slot-glyph"> (für JS-Unequip-Restore)ivUpdatePdSlot(uid,slot,name,glyph,img) — neuer 5. Parameter; erstellt <img> per JS wenn nötig, blendet Glyph aus/ein; Unequip-Fall: Bild ausblenden + Original-Glyph aus data-orig-glyph wiederherstellenbuyable/buy_price → kaufbar/price_gold)buyable → korrekt: kaufbarbuy_price → korrekt: price_goldseed_fix_almberg_loot_kaufbar.php — 5 Almberg-Loot-Items mit korrekten Spalten (idempotent)seed_fix_raeuberset_kaufbar.php — 7 Räuber-Set-Items mit korrekten Spalten (idempotent)require/Database.php)buyable (Combat-/Item-KI)deploy.bat / robocopy aus CLAUDE.md entferntRelease erstellen, Paket herunterladen, Paket installieren)deploy.bat-Aufruf, ersetzt durch admin_patch-Sequenz mit SeedRunner-Idempotenz-HinweisbuildPatchZip()), nicht in einem Shell-Scriptinstall.php ODER deploy.bat erwähnen — beide existieren nicht"core/CombatProcs.php, core/CombatService.php, require/CombatSession.php)forCharacter($char) — liest alle equipped items des Chars, parsed mod_data.bonuses, aggregiert pro Bonus-Typ (additives Stacking, Cap 95% pro Stat). Returnt Map ['poison_chance'=>15, 'crit_chance'=>8, ...].roll($percent) — Hilfsfunktion für Proc-Würfe.addEnemyStatusEffect($idx, $type, $rounds, $value) — Status (poison/bleed/burn) pro Gegner-Index speichern. Stack-Verhalten: gleicher Typ → max(rounds) + max(value) gewinnt.tickEnemyStatusEffects() — pro Runde DoT-Schaden ausgeben + decrement, abgelaufene Effekte entfernen. Returnt Liste mit [enemy_idx, type, damage] für Log-Ausgabe.enemies[idx].status_effects als Sub-Array — bei JSON-Persist automatisch mit.↯ Erstschlag! beim First-Strike-Trigger✦ Kritischer Treffer! (bestehend, jetzt auch durch Item-Crit-Bonus)☠ Vergiftet! -N LP/Runde (3 Runden) beim Poison-Apply⚔ Blutung! -N LP/Runde (2 Runden) beim Bleed-Apply♥ Lebensraub: +N LP beim Lifesteal-Trigger◈ Pariert! beim Item-Parry☠ Gift an <Name>: -N LP / ⚔ Bluten an <Name>: -N LPrequire/Loot.php, seed_raeuber_special_loot_pools.php)"kampfkunst_filter": [1,2,3] — nur wenn $char->kampfkunst() in dieser Liste"min_level": N — nur wenn $char->level() >= N (bonus für später)seed_raeuber_special_loot_pools.php)require/Loot.php, core/CombatService.php, patch.sql)creature_definitions.special_loot_pool JSON — Mob-spezifischer Pool zusätzlich zur Standard-Loot-Tabelledungeon_definitions.special_loot_pool JSON — Dungeon-globaler Pool (gilt für alle Boss-Wins im Dungeon)items.item_level TINYINT — Item-Exemplar-Level für UI/Sortierung und spätere Bonus-SkalierungrollPool($pool) — würfelt einen Pool, returnt getroffene SlugsrollCombinedPools(...$pools) — kombiniert mehrere Pools (Mob + Dungeon)rollQuality($difficulty) — gewichteter Qualitäts-Wurf, Verteilung shiftet mit Difficulty:rollAndGive($char, $pools, $itemLevel, $difficulty) — komplette Pipeline: rollt Pool, würfelt Qualität, ruft Item::giveWithQuality (Item-KI), setzt items.item_level. Fallback auf Item::give wenn Item-KI's API noch nicht aktiv ist.itemLevelFor($baseLevel, $difficultyShift) — Item-Level = Mob-Basis-Level + Difficulty-Shift (min. 1)Dungeon::complete → loot_table) wird der Special-Pool gerolltCreature::get($bossSlug)['special_loot_pool'] + Dungeon-Pool aus $dungeonDef['special_loot_pool']Loot::rollAndGive durchgereicht$allItems mit slug/name/qty/quality/item_level — Loot-Box zeigt siemod_data der Items wird gerollt aber noch nicht im Kampf berücksichtigt. devstatus dun-19: invisible Buffs syncen + Proc-Chancen in CombatService verdrahten + Gegner-Status-Effekte (enemy.status_effects-Sub-Array in CombatSession).seed_fix_bauernset_waffe_schild.php)bauer_waffe + bauer_schild aus dem Set entfernt (set_slug = NULL)bauer_waffe bekommt eigenen Stat: ATK +1bauer_schild bekommt eigenen Stat: DEF +1item_sets.bauernruestung: required_pieces 8 → 6seed_raeuberset.php, require/Item.php)raeuber_schwert — waffe, ATK +1, Bonus-Pool: poison/bleed/crit/first_strike/lifestealraeuber_schild — schild, DEF +1, Bonus-Pool: parry/bleed/critraeuber_bogen — waffe, ATK +1, Bonus-Pool: poison/crit/first_strikeraeuber_armbrust — waffe, ATK +1, Bonus-Pool: poison/crit/first_strikeraeuber_koecher — schild-Slot (kein neuer Slot), INI +1, Bonus-Pool: crit/first_strike/poisonraeuber_stab — waffe, ATK +1, Bonus-Pool: lifesteal/crit/poisonraeuber_fokus — schild-Slot, INI +1, Bonus-Pool: lifesteal/crit/first_strikeimg-Feld → equip/sets/{set_slug}/{slug}.webp → equip/weapons/{slug}.webp → items/{slug}.pngtemplates/assets/equip/sets/bauernruestung/{slug}.webptemplates/assets/equip/weapons/raeuber_*.webpraeuber_armbrust hat kein eigenes Bild → img-Fallback auf armbrust.webp via SeedBONUS_POOL-, BONUS_LABELS- und BONUS_RANGES-Konstante hinzugefügt↯, Bezeichnung: „Erstschlag-Chance", Wertebereiche identisch mit crit_chance'charm' => 'CHR' in alle vier statNames-Maps eingetragen (Kachel-Chip, Detail-Panel, Set-Bonus-Panel, JS sn-Objekt)require/Item.php)Item::giveWithQuality($ownerId, $slug, $quality, $forcedBonuses) — gibt Item mit Qualität + rollt BoniItem::rollBonuses($quality, $poolJson) — würfelt N Boni aus Pool, liefert [{type,value}]null → globaler Pool{"pool":["poison_chance","crit_chance"]} → nur aus diesen Typen würfeln{"fixed":[{"type":"crit_chance","value":10}]} → immer diese Boni (kein Roll)require/Item.php)QUALITY_LABELS — deutsche Präfixe (Fein / Makellos / Meisterhaft / Legendär)QUALITY_MULTIPLIER — Stat-Faktor: ×1.15 / ×1.30 / ×1.50 / ×2.00getRarity() — Seltenheit steigt mit Qualität (fine=+1, superior=+2, masterwork=+3 Stufen)iv-quality-badge) wenn quality ≠ normaliv-stat-chip-boosted)iv-stat-boosted)ivDetailHtml() kennt QUAL_MULT-Map und wendet Multiplikator clientseitig an.iv-quality-badge.iq-*, .iv-quality-line.iq-*, .iv-stat-chip-boosted, .iv-stat-boosted bseed_raeuber_veteranen.php)rachsüchtig (= bei 0-Schaden-Treffer +2 ATK nächste Runde)require/Dungeon.php, combat.php, patch.sql)dungeon_definitions.phases JSON — Array von Phase-Overridescharacter_dungeon_runs.phase_id VARCHAR(40) — leerer String = Basis-Phasecharacter_dungeon_clears PK erweitert um phase_id (jede Phase × Difficulty hat eigene Clear-Statistik + First-Clear-Milestone)resolvePhase($char, $def) — iteriert phases JSON, returnt erste matchende Phase. Trigger-Typen quest+status, flag, flag_not (AND-verknüpft).get($slug, $char=null) — neuer optionaler Character-Parameter. Wenn gesetzt: aktive Phase wird aufgelöst und sämtliche Override-Felder gemerged (monster_sequence, boss_slug, difficulties, hp_restore_*, between_fight_buff_slug, monster_rewards_enabled, loot_table_id, name/description/intro/outro/image_path). Phase-Meta in _phase.startRun() schreibt phase_id mit (eingefrorener Zustand zum Run-Start).recordClear und hasCleared phase-aware.dungeon_raeuberlager_muehlenbach_schwer_v2).Dungeon::get($slug, $character) — Phase automatisch aufgelöst.phase.skip_intro=true + default_difficulty=… → Intro übersprungen, Run direkt mit fester Difficulty.almhausen.php?op=bach erweitertcombat.php?type=dungeon&op=intro&slug=raeuberlager_muehlenbachDungeon::hasCleared()-Abhängigkeit entfernt — Quest-Status ist die Single Source of Truthalmhausen.php)alm_t4_kellerproblem — Button wenn T-3 abgeschlossen (T-3 auto-completes beim Brett-Visit)alm_t6_muehle — Button wenn T-5 abgeschlossen; sonst kursiver Hinweis⚔ Läuft (jade) / ✓ Abgeschlossen (dim)?annehmen=SLUG&_t=TOKEN — CSRF via almhCsrf('brett_annehmen'), Whitelist $brettAcceptable🗡 Kämpfen → combat.php?type=quest&slug=waldraeuberalmhausen_bach.webp (file_exists-Guard)seed_quest_alm_t7c_bach_phase2.php)kill: raeuberhauptmann_dunkler × 1 (analog Phase 1, Boss-Kill schließt ab)t7c_angebot.lines — neue Story-Phrasierung („Diesmal anders. Stiller. Die haben aus der ersten Niederlage gelernt.")t7c_angenommen bekommt navigate: "combat.php?type=dungeon&op=intro&slug=raeuberlager_muehlenbach" — analog zu T-6, Auto-Sprung in den Dungeon nach Quest-Annahmestrassenraeuber_veteran, schattenschurke_veteran, raeuberhauptmann_dunkler als Kreaturen anlegen. Bis dahin ist die Phase „bewaffnet aber nicht abfeuerbar" — Quest annehmen geht, aber Dungeon-Run scheitert beim Spawn der ersten Veteran-Mob.op=bach Atmosphäre + Gate um V2-Trigger erweitern (siehe Test-Plan in todos/quest_raeuberlager_phase2_neuer_angriff.md Abschnitt 4–5).todos/dialog_system_cond_refresh_after_effects.md)seed_fix_ernst_post_completion.php)ernst_sack/muehle_almhausen intro):t6_nach_sieg + t7c_abgabe bekommen navigate: "almhausen.php" → nach complete_quest wird die Seite gewechselt → frische Cond-Eval beim nächsten Ernst-Besuch. Umgeht den Live-Update-Engine-Bug.seed_fix_hilde_zimmer_schulden.php, dialog.php, require/DialogSystem.php, almhausen.php)quest_not_done → quest_not_active (seed_fix_quest_alm_t6_intro_choice_cond.php)replaceUrl() + Quest-Nav nav_redirectscore/Router.php: neue Methode replaceUrl(string $urlPrefix, string $newUrl) —require/Quest.php: applyQuestNav() verarbeitet jetzt auch nav_redirects JSON-Feldpatches/current/patch.sql: ALTER TABLE quest_definitions ADD COLUMN nav_redirects JSON NULL.wizard.php op=ausweis: "← Zurück"-Link entfernt — verhindert vorzeitiges Verlassencommon.php: Fallback-Check nach Charakter-Load — wenn wizard_done-Flag fehltwizard.php op=finish: setzt wizard_done = true in prefs['flags'] bevor Redirect.patches/current/seed_wizard_done_backfill.php: Backfill-Seed für bestehende Charaktereitem_badge_pending global in Output::render() zentralisiertcore/Output.php: konsumiert $_SESSION['session']['item_badge_pending'] vor demalmhausen.php: lokalen item_badge_pending-Block in op=markt entfernt (jetzt zentral).templates/layout/landing.php: E-Mail-Input (#lf-email) erhält tabindex="-1"nav_redirects: ermöglicht deklarative URL-Umleitungen per Quest-Stateitem_badge_pending zentralisiert: Badge-Animation läuft jetzt auf ALLEN Seitenalmhausen.php, seed_fix_quest_alm_t6_remove_navextras.php)active → Atmosphären-Text + Gefahr-Text + Kämpfen-Buttoncompleted → „Am Bach ist es ruhig. Die Räuber sind fort…" (kein Button)none → kein Zugang zum Bach (Redirect zu almhausen.php)core/CombatService.php)edahnien.css, game_nav.php, combat.php)edahnien.css: span.nav.abort bekommt parallel zu a.nav.abort die roten Farben + Hover-Variante. Plus :focus-visible-Outline für Tastatur-Nutzung.templates/layout/partials/game_nav.php: Span-Buttons bekommen role="button" + tabindex="0" (außer wenn dim) + onkeydown für Enter/Space-Aktivierung.combat.php: dim von den Auto-Kampf-Buttons entfernt — sie sind ab Page-Load interaktiv (JS dimmt nur noch während _busy-States).core/CombatService.php, ajax/combat_ajax.php, common.php)raeuberhauptmann.mp4)combat.php, seed_fix_dungeon_raeuberlager_bg.php)combat.php)seed_fix_bauernset_bonus.php)dungeon_definitions — Blueprints (slug, monster_sequence JSON, boss_slug, difficulties JSON mit level_shift+loot_mult, hp_restore_per_fight, hp_restore_type, between_fight_buff_slug, monster_rewards_enabled, loot_table_id, daily_limit, cooldown_minutes, image_path)character_dungeon_runs — laufende + abgeschlossene Runs (state ENUM: active/completed/fled/died, monster_queue JSON nach Shuffle, current_index)character_dungeon_clears — per (character × dungeon × difficulty) Clear-Statistik für First-Clear-Bonus + Cooldown-Trackingcombat_sessions.dungeon_run_id — Verknüpfung CombatSession → Runget/all/save/deactivate — Definitions-API (Admin)canEnter — Tageslimit + Cooldown-Check (real-day-boundary)startRun — Mob-Queue shuffled, Boss am Ende fixactiveRun / currentEnemySlug / isBossFight / progress / difficulty — UI-HelperonFightWon — HP-Restore (flat/percent) + Buff::apply + index++complete — Boss-Loot mit difficulty.loot_mult, Milestone bei First-Clearflee / onDeath / abortMidFight — Run-CleanupCombatSession::start($char, $creature, $mode, $dungeonRunId=null) — neuer optionaler ParameterCombatService::applyWin — verzweigt: Dungeon-Run? Mob-Belohnungen je nach monster_rewards_enabled, dann Dungeon::onFightWon → bei Boss-Sieg Dungeon::completeCombatService::runRound — neues outcome 'dungeon_next' für Mid-Fight-Sieg (UI zeigt Zwischen-Screen statt Sieg-Screen)Dungeon::onDeath bzw. Dungeon::flee werden automatisch aufgerufen?type=dungeon&op=intro&slug=… → Difficulty-Wahl-Seite (3 Kacheln Leicht/Normal/Schwer mit Level-Shift + Loot-Mult Anzeige)?type=dungeon&slug=…&difficulty=… → Run starten ODER fortsetzen (activeRun-Check). Creature wird per Level-Shift skaliert.dungeon_flee — Aufgeben zwischen Kämpfen (state→fled, redirect zur Stadt)outcome=dungeon_next Branch → Zwischen-Screen mit Progress-Bar (X/Y), HP-Restore-Animation, Buff-Anzeige, „Weiter zum nächsten Kampf"-Button + Aufgeben-Buttonconfirm()-Dialog vorher („Run wird zurückgesetzt")dungeonFleeConfirm() → POST dungeon_flee → Redirectfirst_clearDungeon::activeRun !== null → Bestätigungsformular: „Run aufgeben & Tag beginnen" oder „Zurück in den Dungeon". Bestätigt → Dungeon::flee('newday_confirmed') + weiter zum normalen Newday-Flow.CombatSession.dungeon_run_id aktiv → Dungeon::abortMidFight → Run als Flucht, Session weg, Toast-Flag in Session.raeuberlager_muehlenbach)raeuberhauptmann_loot Tabellecombat.php?type=dungeon&op=intro&slug=raeuberlager_muehlenbachseed_raeuber_varianten.php)combat_forest_bg.php)almhausen.php?op=bach)op=bach Block in almhausen.php — Gate: T-6 aktiv oder abgeschlossen (oder SU), sonst Redirect zum Dorfplatz🗡 Kämpfen-Button → combat.php?type=quest&slug=waldraeuber (selbes Pattern wie Keller-Ratte)almhausen_bach.webp / .png — beide mit file_exists()-Guard (Bild wird separat eingespielt)$t6QStatus Variable ergänzt damit active/completed separat prüfbar bleibenseed_fix_quest_alm_t6_ernst_sackgasse.php)give_item/take_item neu laden (dialog.php, dlg-system.js)dialog.php: $itemsTaken[] wird jetzt gesammelt wenn Item::take() erfolgreich ist.dlg-system.js autoTerminal(): Nach dem AJAX-Response wird ajax/inventory_ajax.phpseed_fix_quest_alm_t5_markt_bauernset.php)quest_definitions.alm_t5_markt:dialog_trees marta_brey/markt_almhausen Node t5_tausch_beides:ALTER TABLE characters ADD COLUMN IF NOT EXISTS patch_seen VARCHAR(20) NOT NULL DEFAULT '' AFTER laston in patch.sql. Lokal getestet — Spalte angelegt.Character::LEGACY_FIELDS = ['fertigkeiten'] Konstante. filterKnownColumns() ignoriert diese Felder still — kein Log-Spam mehr für bewusst entfernte Spalten. Getter bleibt für potentielles späteres Skill-System.dialog.php, require/Quest.php)tail /var/log/apache2/error.log — mit Prefix [Dialog-Effect] / [Quest-Reward] / [Quest-Deliver] leicht zu greppendebuglog (über Logger::system-Pfad) — schon vorhandendebuglog gewünscht ist → Boy-Scout-Todorender()): $_SESSION['session']['item_badge_pending']-Blockitem_badge_pending-Block (op=markt) entfernt —dialog.php, dlg-system.js, Seed)dialog.php: Erkennt X-Dlg-Ajax: 1 Header → gibt JSON {ok, items_given, exp_delta, gold_delta} zurück statt Redirect. Alle Early-Exit-Pfade (CSRF, Replay, etc.) geben ebenfalls JSON zurück.dlg-system.js autoTerminal(): Sendet X-Dlg-Ajax: 1 Header, liest Response → wenn items_given nicht leer: addInventoryBadge(slug) + vitalReward('item', 1) für jedes Item.dialog.php complete_quest case: liest Return-Value von Quest::complete() → sammelt Item-Rewards in $itemsGiven für die JSON-Antwort.seed_fix_marta_t5_no_reload.php + seed_fix_marta_t5_effects_order.php: Aktualisiert t5_tausch_beides:almhausen.php, newday.php, core/charstats_func_inc.php)op=zimmer: "Ausruhen (LP auffüllen)" — nur sichtbar wenn HP < max UND Cooldown nicht aktivsetHitpoints(maxHitpoints) → setzt alm_zimmer_ausgeruht_heutevitalReward('hp', healed, {newPct: 100}) — HP-Balken füllt sich animiert, "+X LP" schwebt aufnewday.php — Flag wird gemeinsam mit inn_essen_heute gelöschtvitalReward Typ 'hp' in charstats_func_inc.php ergänzt: Bar-Animation + roter Floating-LabelloadEquipmentBonuses(): Set-Boni nach Item-Stats addiert (via Item::getSetBonuses())iq() — bleibt Alias auf initiative (kein separates IQ-Feld, kein DB-Feld)Item::getSetBonuses(int $ownerId): array — prüft ausgerüstete Items gegen item_sets.required_pieces, gibt stat-Deltas zurückbauernruestung — ATT +1, DEF +2, INI +1 bei allen 8 Teilenseed_redo_hilde_t4_bundle.php ersetzt seed_fix_hilde_t4_abschluss + _give_quest + _navigate + _toggle. Bringt hilde_kamm/schenke_almhausen idempotent in den Soll-Zustand: t4_abschluss-Node mit terminal+navigate+complete-T4+give-T5, intro-Choices mit korrekt togglender Sichtbarkeit.seed_redo_marta_t5_bundle.php ersetzt seed_fix_marta_t5_choice_text + _choice_dedup + _tausch + _navigate + _terminal_restore. Bringt marta_brey/markt_almhausen idempotent in den Soll-Zustand: intro-Choices (Quest-active vs. has_item-Fallback), t5_tausch_check ohne quest_active, t5_tausch_beides mit terminal+navigate+give→complete→take_item.admin_patch.php op=release, ti-23)<details>: „⚠ Trotzdem releasen (Seeds als Snapshot überspringen)" — nur wenn man genau weiß warumexit() / die() in Seeds (require/SeedRunner.php, ti-26)exit(0); / exit(); → return; (sauberer Seed-Abbruch ohne Fehler)exit(N≠0); → throw new \RuntimeException("Seed beendete mit exit(N)") (gefangen von SeedRunner als failed)die("msg"); → throw new \RuntimeException("Seed beendete mit die(): msg")RateLimit::cleanupExpired() in setnewday.php (Vorgänger-Handover-TODO)Csrf-Klasse migriert (Token-Schema Phase 2)common.php (ti-after-replay-fix)docs/dialog_referenz.md)terminal: true + navigate kombinierbar — autoTerminal() POSTetquest_none-Condition implementiert (require/DialogSystem.php)terminal: true + navigate kann kombiniert werden —quest_none-Condition fehlte (require/DialogSystem.php)require/DialogSystem.php + ui.css + dlg-system.js)npc_slug / context_slug + direkter Link zum Dialog-Editorstart-Node, depth * 16px):#id + Speaker-Slug + Meta-Tags (start/terminal/navigate/on_show/anim/widget/action/input) + Zeilen-Preview (90Z) + Effect-Badgesnext (action/input): werden als Mini-Liste im Node selbst angezeigt<details>) — pretty-printedDialogSystem::buildSuPanel() + DialogSystem::dfsBuild() — neue private static MethodendfsBuild(): rekursiv, Zyklus-Schutz via $visited-Array, Orphan-Detection anschließend$rawNodes + $treeRow['tree_json'] aus bestehendem Build-Flowalmhausen.php)almhausen.php)almhausen.php + seed_fix_schwarzes_brett_navigate.php)t3_brett_gelesen-Flag → löst T-3-Objective + Auto-Complete ausrathaus_schwarzes_brett.webpkonrad_fels/rathaus_almhausen → Node zu_schwarzes_brett: navigate gefixthilde_kranz/schwarzes_brett → Node brett_selbst: navigate gefixtalmhausen.php)almhausen.php)almhausen.php)seed_fix_hilde_avatar.php)seed_fix_konrad_avatar.php)require/Quest.php)character_quests löschendlg_done:*-Flags aus prefs löschen (Dialog-Replay-Protection zurücksetzen)Item::take() entfernenalmhausen.php)almhausen.php, seed_fix_hilde_t2_autocompete.php)op=schenke: prüft automatisch ob T-2-Objectives erfüllt → Quest::complete() + Quest::accept(alm_t3_jobs)t2_gegessen_node bekommt navigate: almhausen.php?op=schenke → erzwingt Reload (JS-cached Conditions)village.php, seed_fix_t9_edahnien_flag.php)village.php: Guard leitet nicht-freigeschaltete Chars zu almhausen.php um (Admins ausgenommen)edahnien_freigeschaltet wird als Reward von alm_t9_ernte gesetzt (Seed eingespielt)t4_abschluss.navigate = 'almhausen.php?op=schenke' → nach Quest-Complete sofort Redirect + Reloadt4_abschluss-Choice in intro: quest_active: alm_t4_keller ergänzt → Choice unsichtbar nach Quest-Completeseed_fix_marta_t5_tausch.php)quest_active aus Choice 1 ([Fell und Zahn übergeben]) entfernt → nur has_item: rattenfell nötiggive_quest: alm_t5_markt als erster Effect in t5_tausch_beides ergänzt (vor complete_quest) → Quest-State wird sicher gesetzt unabhängig vom Einstiegswegadmin_patch.php, CLAUDE.md)MasterBuilder::build('v' . $version) aufpatch.sql, patch.md, seeds, files.txt) auch schema.sql + gamedata.sql ins ZIPmanifest.json enthält jetzt snapshot-Sektion mit tables_dumped, tables_skipped, errorsop=master + op=master_pack Operationen (UI-Buttons + Code) — redundant seit jeder Pack automatisch master-fähig ist. MasterBuilder.php-Klasse bleibt erhalten und wird intern genutzt.allowPrefix('admin_patch.php?op=master') + allowPrefix('admin_patch.php?op=master_pack') entfernt.op=pack_self (Self-Update für admin_patch.php selbst)op=pack_chain (Multi-Version-Ketten — nicht angetastet, aber profitiert auch von Snapshot-Inhalten in den Einzelpatches)characters, items, houses, chat_messages, …) bleiben unangetastet (siehe MasterBuilder::SKIP_DATA_TABLES)rate_buckets, security_log, character_change_log) ebenfalls ausgeschlossenrequire/SeedRunner.php, admin_patch.php, patch.sql)applied_seeds (filename PK, version, applied_at, status, error_msg, applied_by) via patch.sql — pro Server eigener Tracking-Stand. Damit kann KI → lokal → Test-Server → Live-Server jeweils nur die Seeds einspielen die auf diesem Server noch nicht gelaufen sind.require/SeedRunner.php mit den Methoden:op=''): Banner „X Seeds noch nicht eingespielt" mit Direkt-Link zur Übersicht. Bei failed Seeds rot, bei pending gelb.op=run_seed komplett umgebaut: Status-Tabelle pro Seed (✓ eingespielt / ⏳ pending / ✗ failed), Buttons „Alle ausstehenden einspielen", „Alle erneut (force)", „Pending als extern eingespielt markieren" (Skip ohne Run). Jeder Lauf schreibt Report nach cache/seed-runs/.op=upload (Patch installieren): nutzt jetzt SeedRunner::runZipContent() statt manueller tmpfile-Loops. Idempotenz: bereits eingespielte Seeds werden ↷ übersprungen (nicht silent doppelt ausgeführt). Zusammenfassung am Ende: X OK · Y übersprungen · Z Fehler. Lauf-Report wird archiviert.Read cache/seed-runs/latest.md direkt prüfen was beim letzten Lauf passiert istalmhausen.php + seed_fix_schwarzes_brett_navigate.php)t3_brett_gelesen-Flag → löst T-3-Objective aus + Auto-Complete der Questkonrad_fels/rathaus_almhausen → Node zu_schwarzes_bretthilde_kranz/schwarzes_brett → Node brett_selbstalmhausen.php)schenke_intro: 'Der Schankraum der "Goldenen Ähre"...'flavor_abend: 'Aus der Goldenen Ähre dringt Licht...'almhausen.php)almhausen.php)require/Item.php)templates/css/ui.css + dlg-system.js)▾-Pfeil via ::after auf .dlg-stage.scroll-hint-visiblescrollStage() (= neue Inhalte kommen, scrollen zu Ende)feedback_heldenausweis_scroll_hinweis.md erledigt.templates/css/ui.css)." " in Dialog-Bäumen (seed_fix_dialog_quotes.php)konrad_fels/rathaus_almhausen — ueber_dorf, brief_hinweis, arbeitmarta_brey/markt_almhausen — t5_tausch_checkmilla/dorfplatz_szene — garrik_szenehilde_kranz/schwarzes_brett — brett_zeigen (zwei Zeilen zu einer zusammengeführt)seed_fix_konrad_anmeldung.php)seed_fix_garrik_avatar.php)npc_descriptions Eintrag für garrik: avatar_path = characters/npcs/garrik_portrait.webpgarrik_einwand + garrik_geh speaker von 'player' auf 'garrik' geänderttree['npcs']['garrik'] inline-Sektion mit color #8b7355 ergänztseed_fix_dorfplatz_narrator.php)seed_dialog_milla_zimmer_t2.php)quest_not_active + Hilde bestellen während T-2 (require/DialogSystem.php + seed_fix_hilde_bestellen.php)false zurück wenn Quest-Status === 'active' (Choice verstecken)test-infrastructure (type: major) als gelebte Demonstration der Regel. Trackt 17 Items von Csrf-Klasse über SecurityTester/ScenarioRunner bis Test-Hub-UI; abgehakt sind die heute fertiggestellten Punkte (Test-Hub op=tests, Rate-Limit-Fix tests:step→tests:begin, Item::clearAll für Inventory-Tests, cache/test-results/-Setup auf Testserver). Offen bleiben: seed_test_chars.php auf Live einspielen, BrowserSession::extractCsrf($context)-Bug, TestResultWriter @-Suppression raus, curl_multi-Race-Stress-Test. Das updated-Feld ist auf 2026-05-25 gesetzt.almhausen.php)dlg-system.js)allowPrefix (core/Router.php)allowPrefix lehnt URL mit fremdem Counter ab (bfcache-Bypass-Schutz)addLink generiert exakt-passenden Counter — kein Bypass via PrefixallowPrefix Counter-Erkennung tolerant gegen Reihenfolgeadmin_patch.php — 13 Stellen (Seed-Buttons, Aktionsbuttons, Form-Actions für Release/Pack/Upload/Master, Download-Links, JS-fetch für upload_chunk): Counter überall entfernt. Die ohnehin vorhandenen allowPrefix('admin_patch.php?op=…') decken die counter-freien URLs ab.acclogin.php — Char-Auswahl-Link (?op=logedin&id=…&token=…&c=…) und Wizard-Neu-Link: Counter weg.require/BrowserSession.php — Test-Runner-Login schickt jetzt counter-freie URL (sonst würden HTTP-Szenario-Tests an loginAs() scheitern).common.php + JS-Guard)docs/SICHERHEIT.md Regel 11 ergänzt um Browser-Back-Aspekt + Pflicht-Headerdocs/SICHERHEIT.md Schwachstellen-Liste: neuer P0-Eintrag mit Exploit-Sequenzen + Fix-Beschreibungdocs/sicherheit_tests.md neuer Eintrag mit manueller Test-Checkliste (Tod-Bypass via Back, Trank-Kauf via Back, DevTools-Network-Header-Check)patches/devstatus.json ti-18 als done abgehakt + ti-19 als offene Boy-Scout-Aufgabe (automatisierter Szenario-Test für Cache-Header)op=finish): Setzt prefs['flags']['wizard_done'] = true bevor derwizard_done-Flag (0 DB-Queriesalmhausen.php)schenke_intro: 'Der Schankraum der "Goldenen Ähre"...'flavor_abend: 'Aus der Goldenen Ähre dringt Licht...'require/Item.php)templates/css/ui.css + dlg-system.js)▾-Pfeil via ::after auf .dlg-stage.scroll-hint-visiblescrollStage() (= neue Inhalte kommen, scrollen zu Ende)feedback_heldenausweis_scroll_hinweis.md erledigt.templates/css/ui.css)speaker: "narrator")rgba(160,200,120,.20))'IM Fell English', 'Georgia', serif + font-style: italic — literarische Prosa-Optik--ink-dim) — zurückhaltender als SprecherblasenresolveNpc(): gibt {name:'', avatar:'', color:'transparent', _isNarrator:true} zurück für narrator/erzaehlershow() (statische Slots a/b): erkennt isNarrator, togglet .narrator-row auf der Zeile, blendet av-wrap aus, setzt .narrator-bubble auf Bubble, versteckt spk-label + em-tagappendDialogRow() (dynamische Rows via next:/showNpcResponse): baut av-wrap nur für nicht-Erzähler, setzt korrekte Bubble-Klasse, versteckt spk-label — Erzähler-Nodes in Dialogketten (next:) funktionieren nahtlos.dlg-row.narrator-row + .bubble.narrator-bubble + .narrator-bubble .bubble-text neu::before/::after-Pfeile auf Narrator-Bubblevillage.php)edahnien_freigeschaltet wird am Ende von T-9 ("Die Ernte vor dem Sturm")patches/current/seed_fix_t9_edahnien_flag.php — ergänzt das Flag in T-9 rewardscityUrl() in common.php gibt für almhausen bereits almhausen.php zurück —almhausen.php)adminReset() setzt jetzt Dialog-Replay-Flags zurück (require/Quest.php)combat_forest_bg.php)test-infrastructure (type: major) als gelebte Demonstration der Regel. Trackt 17 Items von Csrf-Klasse über SecurityTester/ScenarioRunner bis Test-Hub-UI; abgehakt sind die heute fertiggestellten Punkte (Test-Hub op=tests, Rate-Limit-Fix tests:step→tests:begin, Item::clearAll für Inventory-Tests, cache/test-results/-Setup auf Testserver). Offen bleiben: seed_test_chars.php auf Live einspielen, BrowserSession::extractCsrf($context)-Bug, TestResultWriter @-Suppression raus, curl_multi-Race-Stress-Test. Das updated-Feld ist auf 2026-05-25 gesetzt.konrad_fels/rathaus_almhausen: ueber_dorf, brief_hinweis, arbeitmarta_brey/markt_almhausen: t5_tausch_checkmilla/dorfplatz_szene: garrik_szenehilde_kranz/schwarzes_brett: brett_zeigen (Split-Quote bereinigt)display_settings_ajax.php op=bio: prüft nach Save ob Bio neu befüllt wurdetemplates/layout/info.php: showQuestBanner() aus IIFE heraus alsrequire/Item.php stSaveBio JS: bei res.quest_progress → ruftrequire/Item.php — neue Methode Item::clearAll(int $ownerId): voidtests/functional/test_inventory.php — 5 Tests rufen Item::clearAlltests/functional/test_dialog_effects.php — der „Kombination set_flagwald_dunkel.png) für{slug}-hq.webp wird als AnimationCOMBAT_STATE.creature.animation — neues Feld mit WebP-Pfad oder ''.COMBAT_STATE.type — neues Feld ('quest' oder ''), steuert End-Screen-Logik.CS.backUrl (dynamisch jeOutput::getBackground() stattriesenratte_keller (Level 1, regenerierend+einmalig) +alm_t3_jobs Objective-Label korrigiert: "Taverne" → "Rathaus"hilde_kamm/schenke_almhausen t3_brett-Node: schickt Spieler jetzt per navigate direkthilde_kranz/schwarzes_brett brett_selbst-Node: gebrochenes navigaterathaus.php op=schwarzes_brett: T-3-Trigger (quest complete + accept + Aushang-Anzeige)Quest::complete() setzt pending_quest_notify mit type='completed'Quest::progressKill() prüft nach Update ob alle Objectives erfüllt und setzt type='progress'Quest::accept() setzt type='new' (war implizit, jetzt explizit)templates/layout/info.php: kicker-div mit ID, JS liest type dynamisch, CSS-Klassen qb-completed/qb-progress$q['quest_slug'] → $q['slug'] (Quest::active() liefert slug-Key, nicht quest_slug)json_decode($q['progress']) → $q['_progress'] (bereits decoded Array aus getWithDef())$npc, $ctx, $nodeKey → $npcSlug, $context, $nodeId in Logger::security()-Aufrufbio_filled war irrtümlich aus den stadtanmeldung-Objectives entfernt wordenop=zimmer)characters.prefs['alm_zimmer_truhe'] = {gold: X, gems: Y} — kein Schema-Changezimmer_truhe_ein / zimmer_truhe_ab / zimmer_gems_ein / zimmer_gems_abalmhCsrf('zimmer_truhe') im URL-Parameter _ttryDeductGold() / tryDeductGems() — atomar (SICHERHEIT.md Regel 9)$_SESSION['session']['almh_msg']t2_zimmer_gemietet-FlagalmhGetTruhe(array $prefs): array — liest Truhen-InhaltalmhZimmerMsg(string $type, string $text): never — Flash + redirect (DRY)einmalig_riesenratte_keller Flag / Quest alm_t4_kellerproblem)alm_t5_markt)alm_t6_muehle)Item::has('eigentumsurkunde') — war komplett offen = Tutorial-Skipop=viewuser&uid=X — neue Ansicht: Account-Karte (login, email, Regdatum, laston, Test-Badge) + alle zugehörigen Charaktere als Tabelle mit Status-Spaltenop=set_superuser setzbar — sowohl auf der User-Ansicht als auch im Char-Detail (op=view)require/CharacterLog.php neu: Whitelist-basiertes Feldänderungs-Log (32 Felder), detectSource() via Backtrace, in Character::save() eingehängtprefs['flags']['inn_zimmer_sicher'] wird bei Zimmerbuchung gesetztnewday.php §5b: Buff ausgeruht_schenke (def+1, expires_type: newday) + extra Rundenhouses.php: Logout-Link (login.php?op=logout) jetzt sichtbar wenn Heimbonus aktiv (haus_auslogort = house_id)newday.php §5c komplett überarbeitet: Bonus nur wenn haus_auslogort === house.id() (explizites Einschlafen nötig)milla / Kontext zimmer_t2intro: 3 Choices — essen / truhe / schlafent2_essen: take_gold 5 + set_flag t2_gegessen (terminal → autoTerminal)t2_truhe → auto-advance → t2_truhe_notizt2_truhe_notiz: set_flag t2_truhe_entdeckt + t2_truhe_freigeschaltet (terminal)t2_schlafen: navigate → almhausen.php?op=schenkeDialogSystem::render('milla', 'zimmer_t2', $character) wenn Quest aktivt2_essen-Choice aus Hildes intro entfernt (Essen jetzt Zimmer/Milla)alm_zimmer_mieten (Gold + prefs setzen),gold_max (Gegenstück zu gold_min)Quest::assignOverdueRent() — Batch-Methode für setnewday._nd_zimmermiete() → Quest::assignOverdueRent().window.vitalReward)core/charstats_func_inc.php: Inline-Script mit window.vitalReward am Ende vontemplates/css/layout.css: @keyframes reward-gold-glow (Glow-Flash auf .gold-val),templates/partials/dlg-system.js updateCharSidebar():next statt choices:[{text:"[zuhören]"}]npcs-Block (speaker:'garrik' statt speaker:'player')milla_anerkennungop=tests)require/SecurityTester.php um Step-API erweitert (analog ScenarioRunner):admin_grotte.php op=tests — neues Skelett mit 5 Tabs:admin_grotte.php op=tests_step (AJAX) — drei Aktionen:parallelRequest($requests) viatests/scenarios/functional/scenario_race_atomic_gold.php (2 Szenarien):tests/scenarios/functional/scenario_race_rate_limit.php (1 Szenario):'../../etc/almhausen.php' → '....etcalmhausen.php' (hässlicher Dateiname, kein echter Bypass weil Slashes weg, aber irritierend)'almhausen.php\evil' → 'almhausen.phpevil' (Garbage angeklebt)'<script>alert(1)</script>' → 'scriptalert1script' (kein .php aber gespeichert + im HTML angezeigt)sanitizeFeedbackFrom() extrahiert das erste[a-zA-Z0-9-]+ erweitert.assertTrue(), assertFalse(), assertNotOnBadnav()op=functests_run schreibt jetzt auch persistente Markdown-ReportsrunAll() um optionalen $dir-Parameter erweitert — kann jetztop=functests_run (Level 4) — lädt tests/functional/tests/functional/test_quest_lifecycle.php (9 Tests): Quest::status / accept /tests/functional/test_inventory.php (10 Tests): Item::give/take/has/count auftests/functional/test_dialog_effects.php (10 Tests): set_flag-Prefs-Persistierung,tests/scenarios/functional/scenario_navigation_edahnien.php (~14 Szenarien):tests/scenarios/functional/scenario_newday_flow.php (4 Szenarien): Turn-Reset,patches/current/seed_fix_quest_stadtanmeldung_objectives.php: Stellt korrektenDialogSystem.php Zeilen 613–625: bio_filled: true + bio_empty: true alstodos/dialog_konrad_stadtanmeldung_bio_gate.md: Todo für Dialog-KI, damit dertake_gold-Case verwendete undefinierte Variablendorfplatz_szene_gesehen),_ajax=1 im POST wird JSONdialog.fb-modal,quest_not_done → quest_active auf anm_start-Choice imt2_zimmer: terminal+effects → navigate: almhausen.php?op=zimmer_mieten"Aufs Zimmer." (condition: flag: t2_zimmer_gemietet) → t2_aufs_zimmert2_aufs_zimmer: [nickt kurz zur Treppe] → navigate: almhausen.php?op=zimmerop=zimmer_mieten: idempotent (Flag-Guard), tryDeductGold(10) atomar,op=zimmer: Guard (Flag muss gesetzt sein), Zimmer-Flavor-Text,op=ausweis): Router::addLink('← Zurück', $abandonUrl) entfernt.op=scenarios_run rendert jetzt nur ein leeres Skelett mit Inline-JS — die echten Test-Läufe gehen via AJAX über op=scenarios_step. Vorteile: User sieht Live-Fortschritt pro Test (Skelett mit Wartesymbol → wird zu grün/rot wenn Test durch), kein 3-6 Sek weißer Bildschirm, Doppelklick-Problem entschärft (Page-Rate-Limit von 5min auf 1min runter, Tests laufen ohnehin via AJAX statt synchron).op=scenarios_step?ajax=1 mit drei Aktionen:TestResultWriter::write($results, $kind, $meta = []) schreibt einen vollständigen Test-Lauf-Report als Markdown nach cache/test-results/{kind}_latest.md (überschreibt) + cache/test-results/history/{kind}_{YYYY-MM-DD_HHMMSS}.md (max. 20 History-Files pro kind, FIFO-Rotation). Format: Summary-Tabelle + Tests nach Status sortiert (error/fail zuerst), Assertions als Bullet-Liste, Exception als Code-Block. Sicherheits-KI / Test-KI kann via Read cache/test-results/scenarios_security_latest.md direkt den letzten Lauf einlesen statt Copy-Paste aus der Browser-UI.require_once 'require/TestResultWriter.php' ergänztCsrf::verify-Pipeline über echte HTTP-Sessions.rate_buckets-UPSERT auch über separate Worker-Prozesse race-safe inkrementiert. Inkl. Cleanup der Test-Reports aus dem feedback/-Ordner.<script>-Body wird 1:1 gespeichert (Beweis dass htmlspecialchars_decode wirklich raus ist) UND beim Render via htmlspecialchars korrekt escaped.badnav_loop_marker jünger als 10 Sek ist + restore-Link wird unterdrückt.require/CookieJar.php — Cookie-Persistierung für HTTP-Sessionsrequire/BrowserSession.php — HTTP-Test-Client (login/get/post/extractCsrf/state/assert*)require/TestRunnerAuth.php — Nonce-basierte Runner-Authentifizierung (per-Request, nicht per-Prozess)require/ScenarioRunner.php — Mini-Framework für Szenario-Tests (analog SecurityTester)patches/current/seed_test_chars.php — drei Test-Accounts (secsim_a, secsim_b, funcsim) + Reset-Methodentests/scenarios/security/scenario_smoke.php — 3 Smoke-Tests: positiv (Login → village.php → 200) + 2× negativ (Login mit ungültigem/falschem Nonce scheitert)patch.sql — DB-Schema: user.is_test_account-Spalterequire/User.php — isTestAccount()-Getterlogin.php — Guard: Test-Accounts können nur mit gültigem Runner-Nonce eingeloggt werdenadmin_grotte.php — neuer Handler op=scenarios_run (Level 4, Rate-Limit, generiert/clearet Nonce)common.php — Bootstrap der neuen Klassendocs/test_architektur.md — Status auf "Phase 1a fertig" + Nonce-Mechanismus dokumentierendocs/SICHERHEIT.md — neuer Abschnitt "Test-Infrastruktur" mit Mitigationenpurchasable → kaufbar, price → price_goldpurchasable → kaufbar, buy_price → price_goldLOGGED_FIELDS): gold, gems, goldinbank, mondsilber,detectSource() via debug_backtrace() — erkennt automatisch aufrufende PHP-Datei + ZeilesetContext() / clearContext() — manueller Override für beschreibendere Quellenrecord() — skippt Zero-Delta-Numeric-Changes, wrapped in try/catch (Logging bricht save() nie ab)getHistory(), getGoldHistory(), getRecentAll(), getFieldLog() für Admin-Abfragen$original[]-Array fängt Pre-Change-Werte vor erstem set()-Aufruf einsave() schreibt nach erfolgreichem UPDATE alle geloggten dirty Fields via CharacterLog::record()tryDeductGold() + tryDeductGems(): expliziter CharacterLog::record()-Aufruf nach atomarem UPDATEspirits TINYINT(2) DEFAULT 0lastday DATETIME DEFAULT '2000-01-01 00:00:00'killedin VARCHAR(100) DEFAULT ''forest_fights SMALLINT UNSIGNED DEFAULT 0characters.name UND user.login / user.emailaddresscedToken(charId)op=viewuser&uid=X (oder op=view&id=Y als Fallback)op=view) als "Berechtigungen"-Panel verfügbarfbSaveRatings() schreibt ratings.json jetzt mit LOCK_EX — keine Korruption bei parallelen rating/delete-Klicksfeedback:post-Context, RateLimit-Integration über shared context, mb_substr UTF-8-Sicherheit, from-Filter weist Path-Traversal/XSS-Versuche abop=keller Stub durch echten Kampf-Start ersetzttype=quest und stadtspezifische Spawn-Location:op=release) um optionale Felder erweitertop=feedback-Handler (ab Level 1) — zeigt alle .md-Dateien aus feedback/ in der Admin-Grotterespawn_type war leer (Seed hatte 'once' verwendet, das nicht im ENUM ist).city=almhausen wird jetztalmhausen.php: Navigation auf Dorfplatz überarbeitet — Kategorien nachdocs/almhausen_stadtplan.md: Ruf-System, Milla, Rettungslager, Schmiede-Korrektur,docs/almhausen_tutorial.md: Vollständige Tutorial-Questreihe (T-0 bis T-9)BrowserSession + drei Test-Charaktere) für beide Weltenpurchasable → kaufbar, price → price_gold (Spaltennamen aus dem Legacy-Schema ersetzt)character_change_log.character_change_log-Tabelle (id, character_id, field, old_value, new_value, delta, source, created_at)spirits, lastday, killedin, forest_fights.DELETE FROM commentary-Zeile ausgebautcommentary, creatures, taunts gelöscht (alle MyISAM/latin1, Special-only, nicht mehr genutzt)op=rathaus — Heldenausweis als $extraContent (versteckt, flex-zentriert); $headscript mit ausShowCard(uid): scrollt .dlg-stage nach unten (letzte Konrad-Nachricht sichtbar) + blendet Karte ein; vollständige Zonen: idc-hero-title (Rasse·Klasse), idc-story (Bio als Stadtchronik-Eintrag mit drop-cap), idc-date-block (Datum), idc-seal mit SVG-Wappenkreisen + ausweis-stamp-Overlaykonrad_fels/rathaus_almhausen integriert (kein Redirect zu rathaus.php mehr); alte Redirect-Nodes werden entfernt; neue Nodes: anm_start, anm_ausweis_zeigen (on_show), anm_unterschreiben (play_ausweis_anim), anm_bio_fehlt, anm_aendern, anm_bereits_erledigtsubmitEffects() liest _node.navigate als Redirect-Ziel nach Ausweis-Animation (statt hardcodiertem rathaus.php)op=anmeldung — fälschlich eingefügte Heldenausweis-Logik entfernt (Almhausen-Anmeldung läuft jetzt vollständig über almhausen.php)bio_filled + bio_empty in choiceAllowed()milestone_award — vergibt Milestone direkt im Dialogplay_ausweis_anim-Flag in submitEffects() — SVG-Unterschrift + Stempel-Animation + Milestone-Overlay.ausweis-card + .ausweis-signing + .ausweis-stamped Styles + Animationenkonrad_fels/anmeldung mit bio_filled/bio_empty/quest_* Conditions (3 Objectives)stadtanmeldung mit 3 Objectives (rathaus_besucht / bio_filled / ausweis_gestempelt) + Milestone in Rewards; giver: konrad_fels / almhausen_rathausbio_filled in objectiveMet() + Popup-Tab-Switchkonrad_fels/rathaus_almhausen-Tree um Stadtanmeldungs-Choicesbuergerausweis — Kategorie urkunde, nicht stapelbar, nicht handelbar, keep_on_dragonkill=1 (bleibt nach Dragon-Kill)buergerausweis zu Quest-Rewards hinzugefügt; Objective-Labels "Mira" → "Konrad Fels" korrigiertstadtanmeldung umgestellt (war start_rathaus)start_rathaus aus quest_definitions + character_quests löschenop=sectests-Handler (Level 4) — rendert sicherheit_tests.md als Eintragsliste mit ☐/☑-Checkboxen; Nav-Link "Sicherheits-Tests" in der Level-4-Sektion; zusätzlicher op=sectests_run-Handler der die automatischen Tests aus tests/security/ ausführt und das Ergebnis (grün/rot/Fehler) anzeigttest() registriert, runAll() lädt tests/security/test_*.php, führt jeden Test in einer DB-Transaktion aus (Rollback am Ende), sammelt Assertions; Helper assertTrue/assertFalse/assertEqual/assertNotEqual; in common.php geladenLogger::security() schrieb in falsches Schema (date, actor, message, context) — diese Spalten existieren in security_log gar nicht (echtes Schema: id, character_id, event_type, detail, ip, created_at). Aufrufe scheiterten still im catch(\Throwable). Fix: korrektes Schema, IP automatisch aus $_SERVER['REMOTE_ADDR'], Signatur jetzt (string $eventType, string $detail = '')Logger::security('dialog_replay_attempt', json{npc, ctx, node, mode, key}) aufgerufen — Admin sieht in der Übersicht ob/wie oft Replay-Versuche kommenop=seclog-Handler (Level 4) — zeigt die letzten 200 Einträge aus security_log mit Aggregat-Übersicht pro event_type (Pills, ab 20× pro Typ in Orange), Tabelle Zeit/Event/Char/IP/Detail, "Log leeren"-Button mit Confirm; Nav-Link "Security-Log (N) (Bypass/Replay)" in der Level-4-Sektion; allowPrefix für F5-Festigkeit$disabledForRequest und $tableExists-Cache. Wenn rate_buckets fehlt: einmal loggen, dann alle weiteren check()/status()/reset()/cleanupExpired()-Aufrufe im selben Request returnen sofort ohne Query → kein 60-fach-Spam im DbErrorLogger. ensureTableExists() prüft via information_schema.tables mit Request-Cacherate_buckets und character_change_log in SKIP_DATA_TABLES ergänzt — Inhalte sind pro-Spieler und gehören nicht in einen Master-Dump (Tabellen-Definition bleibt aber im Schema-Dump)safe_unserialize(?string $raw, mixed $default = []): mixed — kapselt unserialize($raw, ['allowed_classes' => false]) mit Default-Rückgabe. Globale Funktion (kein static), weil sehr häufig genutzt__PHP_Incomplete_Class, verschachteltes Objekt im Array auch geschützt, b:0; Edge-Case dokumentiertrate_buckets Tabelle), Fixed-Window-Counter, atomare UPSERT via ON DUPLICATE KEY UPDATE. API: check() / require() / status() / reset() / cleanupExpired(). Fail-open bei DB-Fehlern. Context-Konvention identisch zur Csrf-Klasse: "modul:aktion"rate_buckets (PK character_id + context, count + reset_at als unix-Timestamp, Index auf reset_at für Cleanup)RateLimit::check($charId, 'shop:buy', 30) als erstes im buy-Op — schützt vor Bot-Käufen (max 30/min = 1 alle 2 Sekunden)RateLimit::check($charId, 'inn:ale', 12) im ale-AJAX — verhindert Buff-Spam (12/min = 1 alle 5 Sekunden)op=pack_chain — bündelt alle Versionen seit dem letzten Master-Paket in einem einzigen ZIPac_check() wurde nur in _legacy/ aufgerufen (pvparena, bank2, dag, gardens, shrine, treasury, chess, markt-buy), die Tabelle accounts_bio und Spalte multiacc existieren nach der Charakter-DB-Migration nicht mehrrequire_once "anticheat.php" entfernt, mit Kommentar dokumentiert warum + Hinweis auf characters.lastip/uniqueid falls Multiacc-Erkennung jemals neu gewünschttokenFor(string $context): string (SHA-256, 16 char hex), verify(string $context, ?string $token = null): bool (timing-safe via hash_equals, liest aus _POST['_csrf']/_GET['_csrf']/_POST['token']/_GET['token']), requireValid() (wirft RuntimeException + Logger::security('csrf_mismatch', …)), hiddenInput() (HTML-Helper). Context-Konvention: "modul:aktion"Router::loadFromDb() zum kontrollierten No-Op gemacht — eval()-Zweig komplett raus, ebenso Legacy-NPC-Block (npc_onscreen) und die _legacy/-SU-Editor-Links (su_naveditor, su_spracheditor, su_navigationseditor). addLinkRaw-Helper entfernt (war nur für die entfernten Links). Falls $_nav_array zur Laufzeit doch Daten enthält, wird ein Eintrag router_loadfromdb_called in security_log geschrieben — sonst silent return. @deprecated-Annotation gesetzt. Aktive Aufrufer: nur index.php:263 (No-Op gegen leeres Array, lib/-Ordner existiert nicht mehr).tryDeductGoldInBank(int): bool — analog zu tryDeductGold für das Bank-Konto, conditional UPDATE auf goldinbank >= ?Item::shopBuy() nutzt tryDeductGold() statt Read-then-Writeop=ale_trinken), Essen (op=essen), Zimmer (op=zimmer), Mara-Schäferstündchen unverheiratet (op=schafer) + verheiratet (op=schafer_verheiratet). Page-Handler bekommen graceful Abort vor dem Buff falls Race auftritttryDeductGold + DialogAjax::fail ersetzttryDeductGoldInBank erweitertdlg_done:<npc>:<ctx>:<node> in prefs['flags'], blockt bei gesetztem Flag mit silent Redirect, setzt sonst Flag VOR Effekt-Ausführung (Crash-resistent gegen Dupe). Author-Override im Node-JSON: "replay": "once" (default) | "daily" | "free"dialogReplayKey($npc, $ctx, $node, $mode) / dialogReplayBlocked($key) / dialogReplayMark($key) — testbar isoliert vom dialog.php-HandlerRouter::snapshot() / restoreSnapshot() — vom SecurityTester pro Test verwendet, damit Tests wie test_router_whitelist.php den Nav-State der laufenden Seite nicht verseuchenRouter::snapshot()/restoreSnapshot() aufgerufen — Tests dürfen Router::clear() etc. nutzen ohne die Ergebnis-Seite zu zerstörenallowPrefix('admin_grotte.php?op=sectests') ergänzt damit F5 auf der Tests-Seite + Run-Seite nicht zu badnav führt (idempotente Admin-Tools, siehe SICHERHEIT.md Regel 11); zusätzliche Nav-Links (Zurück zur Checkliste, Admin-Grotte, Dorf)tryDeductGold(int $amount): bool und tryDeductGems(int $amount): bool — conditional UPDATE mit WHERE gold >= ?, persistiert pending dirty writes vorher, gibt false zurück bei Insufficient/Zero/NegativetryDeductGold() statt read-modify-write — schließt Dupe-Lücke bei parallelen KäufentryDeductGold/Gems() analogRouter::replaceUrl($urlPrefix, $newUrl) — ersetzt die URL aller Nav-Links deren URL mit $urlPrefix beginnt; neue URL wird zusätzlich zur Whitelist hinzugefügt; alte URL bleibt gültig (verhindert badnav bei Doppelklick). Nicht direkt aufrufen — wird von Quest::applyQuestNav() gesteuert.applyQuestNav() verarbeitet jetzt auch nav_redirects (drittes JSON-Feld neben nav_restrictions/nav_extras); SQL-Query + WHERE-Klausel entsprechend erweitert; Docblock aktualisiert.ALTER TABLE quest_definitions ADD COLUMN IF NOT EXISTS nav_redirects JSON NULL AFTER nav_extrasportraitPath() und Wizard-Portrait-Liste indexieren jetzt nur noch .png-Basisdateien — Qualitätsvarianten (-hq.webp, -md.webp, -sm.webp) und Format-Duplikate (.webp) werden übersprungen; vorher zählte jedes Portrait 5× im IndexsetBackground() normalisiert jetzt auf Basis-Pfad (Extension/Suffix-Strip); neue Methoden getBackgroundCss() (CSS image-set + Viewport-Breakpoints) + picture() (responsives <picture>-Element)Output::getBackgroundCss('.info-page::before', ...) ersetztOutput::getBackgroundCss('.scene-bg', ...) ersetztop=zimmer_mieten — Gold abziehen, flag zimmer_gemietet setzen, in Zimmer redirectenop=essen_kaufen — Gold abziehen, HP-Buff anwendenop=schwarzes_brett — Brett anzeigen (optional, navigate-Fallback)op=keller — Keller-Kampf initiieren (Tutorial-Ratte)op=marta_tausch — rattenfell + giftzahn nehmen, klassenspezifische Starterausrüstung gebenop=ernst_waldraeuber — Waldräuber-Kampfsequenz (2–3 Gegner)op=ernst_tagesaufgabe — tägliche Mühlen-Aufgaben anzeigenop=kutsche — Reise-Interface öffnentake_gold — zieht Gold atomar via Character::tryDeductGold() ab; loggt Insuffizient-Fälle in security_log; Docblock aller Effect-Typen ergänzt.CombatService::runRound() aus der Session gesichert; bei outcome === 'win' ruft Quest::progressKill() für jeden Feind-Slug auf → T-4/T-6/T-7c/T-9 Kill-Objectives zählen automatisch.require_once 'require/Quest.php' an den Dateianfang verschoben (war inline im rathaus-Op). Neue Ops: op=lager (Ausbildungslager, klara_frost/klara_t8), op=bauerngasse (Helga Brandt, helga_brandt/helga_t9), op=keller (Stub-Redirect zur Schenke bis CombatSession::start() fertig). Dorfplatz-Hauptseite: Milla-Einmalig-Szene (rendert milla/dorfplatz_szene wenn stadtanmeldung completed und Flag dorfplatz_szene_gesehen noch nicht gesetzt). Bedingte Nav-Links: Ausbildungslager erscheint wenn alm_t7a_lieferung completed (oder SU), Bauerngasse wenn alm_t8_kampfkunst completed (oder SU).op=keller ist Stub — braucht CombatSession::start() mit once-Kreatur und Redirect zu combat.phpop=pack_mini) — Kategorien wählen → ZIP herunterladenop=export_seed) — quest_definitions + dialog_trees aus DB → PHP-Seedop=selective) — ZIP hochladen, nur ausgewählte Kategorien deployenop=fix_perms in admin_grotte.php) — chmod + chown-Befehlheimat_bonus-Feld im Loot-Rückgabe-ArraymaxHitpoints() (DB-Rohwert), jetzt maxHitpoints() + dragonHpBonus() + Buff::statBonus('maxhp') wie charstats_func_inc.php; $effectiveMaxHp auch als Cap für Wetter-Bonus und Haus-Komfortbonusop=pack_code — Code-only ZIP ohne Bilder (für Server mit kleinem Upload-Limit)buildPatchZip() nutzt addFile() statt addFromString() — kein Memory-Exhaustedurl('assets/...') → url('../assets/...') in landing.css + combat.cssGAME_VERSION Fallback-Konstante auf '0.1.0b' aktualisiert (war '0.0.7e')?module=X?op=Y → ?module=X&op=Y — Links in Admin-Modulen landeten immer in der Grotte? statt & als Query-Parametertrenner → PHP las module=charedi?op=view als Module-Name, kein Match in moduleMap → Redirect zur Grotteurl('assets/...') → url('../assets/...') in landing.css + combat.cssurl('assets/...') → url('../assets/...') in landing.css + combat.csslib/controll.php → _legacy/ (checkday/alive-Guard/automaster, alles überholt)lib/ev_engine.php → _legacy/ (eval()-RCE, dead code, abhängige Tabellen weg)core/navigation.php → _legacy/ (durch Router.php ersetzt)core/quest_func_inc.php → _legacy/ (durch require/Quest.php ersetzt)core/npc_func_inc.php → _legacy/ (durch require/DialogSystem.php ersetzt)superuser.php → _legacy/ (alte Admin-Grotte)hidden-Attribut direkt im HTML gesetzt (info.php + interactive.php)showMilestone() entfernt hidden, closeMilestone() setzt es zurück.htaccess neu: PHP-Upload-Limits 32M/64M (mod_php + mod_php8)admin_patch.php: Upload-Fehlermeldung verbessert, Formular zeigt aktives Limitrequire/Character.php: Getter für tote Spalten entfernt (familyname, beruf, labyrinthTurns, blut)core/user_func_inc.php: Tote editierbare Felder aus $char_extra_infos entferntdragon.php: Tote Einträge aus der nochange-Liste entferntdocs/migration_characters.sql: CREATE TABLE auf bereinigtes Schema aktualisiertadmin_charedi.php)intro-Node: "Ich möchte beim Bautrupp vorbeischauen." (nur bei haus_keines)bautrupp_schauen: Siegbert erklärt kurz dass Hannes nebenan steht undgoto_bautrupp navigiert wie gehabt zu bautrupp.phpbautrupp.php direkt erreichen, nicht über Siegberthaus_keines-Fall schon drin (sagt "kein Grundstück — kein Haus, geh zu Siegbert")seed.php automatisch eingespielt (liest docs/dialog_siegbert_burgviertel.json)op=run_seed: listet alle seed*.php in patches/current/ auf<pre>/^seed[\w\-]*\.php$/ validiert, kein Pfad-Traversalop=run_sql führt immer den vollen Patch aus (inkl. DROP TABLE) — zu destruktiv fürop=shop_kaufen entfernt$shopToken entferntrequire/DbErrorLogger.php (neu): file-basierter Logger für DB-Fehlerrequire/Database.php:admin_grotte.php:logs/.htaccess (neu): Require all denied — kein direkter Web-Zugriff auf Logs$db->lastError() sichtbar — flüchtig, nur der letzte Fehlerpatch.sql: DROP TABLE IF EXISTS items, item_definitions, itemtransferitems hatte Schema (id, name, class, value1, owner, gold, gems, …) — kein definition_idItem.php JOIN auf d.id = i.definition_id schlug fehl: "Unknown column 'i.definition_id' in 'on clause'"CREATE TABLE IF NOT EXISTS-Falle wie bei houses — DROP garantiert sauberes Schemapatch.sql: DROP TABLE IF EXISTS für house_keys, house_furniture, house_rooms,owner → owner_id) entfernt — wird nicht mehr gebrauchthouses-Tabelle hatte owner-Spalte statt owner_id → INSERT schlug mitCREATE TABLE IF NOT EXISTS überspringt stillschweigend falls Tabelle existiert → Schema-Mismatchcommon.php: Router::clear() wird bei AJAX-Requests (?ajax=1) NICHT mehr aufgerufenitems-Tabelle: expires_at TIMESTAMP DEFAULT NULL → expires_at TIMESTAMP NULL DEFAULT NULLNULL in der Spalten-DefinitionsplitSqlStatements(): neuer Zeichenparser ersetzt explode(';', $sql)runSqlStatements(): neues Output-Format.patch-ok-group, .patch-ok-summary, .patch-ok-list für eingeklappte OK-Gruppeadmin_patch.php: $db->lastError → $db->lastError() (öffentliche Methode)Database::$lastError ist private — direkter Zugriff warf Fatal Error (Throwable)burgviertel.php: House::create() in try-catch — RuntimeException führte zu HTML-Outputburgviertel.php: Gold-Abzug erst NACH erfolgreichem House::create() (atomische Reihenfolge)docs/dialog_siegbert_burgviertel.json: spoken: "Bezahlt." an der Bezahlen-Choicetemplates/partials/dlg-system.js:dialog_speed.php (neu): POST-Endpoint speichert prefs['dialog_speed'] (1/2/3) in DBrequire/DialogSystem.php: Speed-Pref aus Prefs lesen, speed + speed_token in JS-Daten,templates/partials/dlg-system.js (v1.4):templates/edahnien.css: .dlg-speed-icon — absolut positioniert links vom Reset-Iconrequire/Item.php (neu): Statische Inventory-Klassecore/Output.php: $inventoryHtml Static + getter/settertemplates/layout/partials/game_topbar.php: 🎒 Rucksack-Button (vor Post)templates/layout/info.php: Inventar-Popup (#popup-inventar) eingefügtburgviertel.php: Eigentumsurkunde-Item geben nach Grundstückskaufbautrupp.php:house_ajax.php: neuer op=moebel_einbauenhousemodules/_gold_ui.php: Zeigt "Möbel einbauen"-Form wenn kein Mobiliar + Item im Inventarpatch.sql: item_definitions + items Tabellen + Seeds (5 Baustoffe + 3 Möbel + Eigentumsurkunde + 5 Fluchkessel-Zutaten + Blaupause)require/DialogSystem.php: has_item / has_item_not Conditions verdrahtet mit Item::has() (waren Stubs)fluchkessel.php: op=gegengift + op=trank — echte Implementierung statt Stubsdialog.php: give_item + take_item Effect-Handler implementiertpatches/current/seed_burgviertel.php (neu): Bugfix für kaputte Dialog-JSONs als standalone Browser-Seedpatches/current/seed.php: Siegbert/burgviertel + Hannes/bautrupp Bugfix in seed.php integriertpatches/current/patch.sql: avatar_path für Siegbert + Hannes auf characters/npcs/*_portrait.png gesetzt (war leer) + sofortige UPDATE-Statements für bereits deployed DBstemplates/partials/dlg-system.js v1.3: Klick-to-Skip — Klick auf Stage während Typewrite deckt Text sofort auf (wie in VN/JRPGs). _skipFns-Map + finish()-Funktion in typewrite() + Click-Handler auf Stage in dlgInit. Buttons/Links im Stage sind ausgenommen (closest-Check).Router::allowPrefix('village.php') wird NICHT in die DB persistiert — nur allowednavs wird via saveuser() serialisiert. Dialog-Nodes mit navigate: "village.php" erzeugten daher Badnav. Fix: Router::allow('village.php') (ohne Parameter) explizit in allen betroffenen Seiten ergänzt: burgviertel.php, kutschenhof.php, arena.php, burgschmiede.php, markt.php, fluchkessel.php, bank.phpburgviertel.php: AJAX-Erfolgsmeldung nach Grundstückskauf zeigt jetzt die Eigentumsurkunden-Übergabe im Dialog-Chat; Spielername via $character->name()docs/dialog_siegbert_burgviertel.json: gehen-Node von terminal:true auf navigate: village.php geändert (Dialog verlässt die Seite statt hängenzubleiben); grundstueck_gekauft Choices angepasstpatches/current/patch.sql: INSERT für house_locations: burgviertel_edahnien direkt in patch.sql — reicht nun install.php statt separater Seed-Ausführungdocs/dialog_siegbert_burgviertel.json: grundstueck_kaufen_bestaetigung Choice "Einverstanden" → "Bezahlen" mit cost: "{grundstueck_preis} Gold"; grundstueck_kaufen_ja auf lines:[] (Action-Node-Pattern — kein Text vor AJAX, Urkunden-Übergabe kommt aus AJAX-Response)patches/current/seed.php: house_locations: burgviertel_edahnien eingebaut (war nur in seed_haussystem.php standalone) — fix für "Dieser Standort ist nicht verfügbar"-Bug beim Grundstückskaufnewday.php: Grundsteuer-Abzug entfernt (durch Verfall-System ersetzt, wäre unfair für Spätstarter)docs/dialog_siegbert_burgviertel.json: Grundsteuer-Erwähnung aus grundstueck_info-Node entferntburgviertel.php: $grundsteuerPreis + grundsteuer_preis extraVar entferntpatches/current/seed_haussystem.php + seed.php: grundsteuer_preis = 0 (Spalte bleibt im Schema, wird nicht verwendet)item_definitions.slug UNIQUE KEY für stabilen Lookup ohne ID-Hardcoding$inventoryHtml Global-Variable, kein AJAX-Reloadbautrupp_shop (kein Dialog-Token — Shop ist kein Dialog-Action)house_ajax.php (neu): POST-Handler für alle Haus-Aktionenhouses.php: $houseToken + houseFlashHtml() + Flash vor Modul-Outputhousemodules/_gold_ui.php (neu): Gemeinsames Gold/Gems-UI für alle Gold-Räumehousemodules/stube.php (vollständig):housemodules/privatgemach.php (vollständig): Gold-UIhousemodules/schatzkammer.php (neu): Gold-UI (Besitzer + Schlüsselinhaber)housemodules/kueche.php (neu): Komfort-Infohousemodules/arbeitszimmer.php (neu): +5% EXP Infohousemodules/waffenkammer.php (neu): Item-Lager Stubhousemodules/keller.php (neu): Extra-Lager Stubhousemodules/gaestezimmer.php (neu): Gast-Buff Stubtemplates/edahnien.css: .btn-evo, .btn-evo--sm, .btn-evo--danger, .input-evohouses.php (neu): Innenansicht + Raum-Dispatcherhousemodules/stube.php (neu): Atmosphären-Text + Schlüssel-Liste (read-only, Vergabe Schritt 8)housemodules/privatgemach.php (neu): Gold/Gems pro Möbelstück anzeigen (Stub für Schritt 6)templates/edahnien.css: .house-rooms, .house-room-link, .house-room-lockedpatch.sql: content_strings für houses.php (15 Texte)newday.php: Neuer Schritt 5c (nach Schlafen-Flags, vor save())setnewday.php: Neue Funktion _nd_houses(), nach _nd_maintenance() aufgerufenburgviertel.php: Komplett neu — ersetzt alten Platzhalterbautrupp.php (neu): NPC Hannesdocs/dialog_siegbert_burgviertel.json (neu): 11 Nodes, condition-basiert auf Haus-Flagsdocs/dialog_hannes_bautrupp.json (neu): 25 Nodes, alle 4 AJAX-Operationenrequire/DialogSystem.php: Neue Haus-System-Flags (hat_grundstueck, haus_keines, haus_plot,patch.sql: NPC-Beschreibungen (siegbert, hannes) + Dialog-INSERTs + content_stringsrequire/House.php: Model für houses-Tabellerequire/HouseRoom.php: Model für house_rooms-Tabellerequire/HouseKey.php: Model für house_keys-Tabellepatch.sql (Korrektur): houses.status ENUM ergänzt um 'plot' als erster/Default-Wertpatch.sql: 5 neue Tabellen (house_locations, houses, house_rooms, house_furniture, house_keys)patch.sql: characters um ausgeruht_stufe + haus_auslogort erweitertpatch.sql: alle Haussystem-Settings (Kosten, Limits, Buff-Werte)seed_haussystem.php: house_locations (Burgviertel Edahnien)_legacy/houses/: altes Haussystem gesichert (6 PHP-Dateien + 20 housemodules)kutschenhof.php: Komplett neu — NPC-Dialog Minna statt statischer Listekutsche.php (neu): AJAX-Handler für op=reisen und op=eilreisendialog.php: Neuer Effekttyp give_gold (für Reisepass-Belohnung)patches/current/seed_kutschenhof.php: NPC Minna, Dialog-Baum (25 Nodes),patches/current/patch.sql: CREATE TABLE travel_coststodos/todo_dialoge_integration.md: Dialog #19 (Minna/Kutschenhof) auf ✅ gesetztreise_almhausen … reise_ewigbluete) weil fetchActioncondition_override aus dem Spec-JSON nicht implementiert (nicht standard) —angekommen-Node navigiert zu village.php (Character-Stadt wurde bereits geändert)dialog_editor.php: Neuer 👁 Vorschau-Button in der ToolbarcollectTree() liest jetzt Node-Key aus dem Eingabefeld (Umbenennen funktioniert)addLine() focus nach appendChild korrigiert$v($formJson) im <script>-Tag durch json_encode(..., JSON_HEX_TAG) ersetzt (Nodes wurden nicht geladen)toggleCard etc.) auf window exportiert (waren im IIFE nicht erreichbar)dialog_editor.php: Komplettneuschreibung — rohe JSON-Textarea durch vollständigen visuellen Editor ersetztisSuperuser(4), Router, NPC-Slug-Querytoggle, delete — identische Logiknpc_slug, context_slug, tree_json, active) — identischrequire/DialogAjax.php (neu): Sicherheits-Klasse für alle Dialog-AJAX-Handlertemplates/partials/dlg-system.js: dlgAjaxUrl() hängt dlg_token, dlg_npc, dlg_ctx an jeden AJAX-Callinn.php, mara.php: DialogAjax::verify() in AJAX-Handlern ergänztrequireCondition()-Fehler schreibt einen Eintrag in security_logsecurity_bypass_threshold (default 5) Failuresprefs['security']['flagged_for_review']settings-Tabelle konfigurierbar ohne Code-Änderungtemplates/partials/dlg-system.js: neue Funktion showInput(uid, inp)templates/edahnien.css: Styles für .dlg-input-wrap, .dlg-input-field, .dlg-input-btn, .dlg-input-errordocs/dialog_spec.md: neue Section §7 dokumentiert das input-Property vollständig (v1.2)mara.php (neu): Maras Zimmer-Seite mit DialogSystem::render('mara', 'zimmer', $character)inn.php: Whitelist um mara.php erweitert, Nav-Link "Mit Mara aufs Zimmer" zeigt auf mara.phppatches/current/seed.php (neu): Seed für npc_descriptions (Mara) und dialog_trees (mara/zimmer)admin_patch.php: buildPatchZip() und buildSelfUpdateZip() verwenden jetzt addFromString() + file_get_contents() statt addFile() für alle Dateien inkl. CHANGELOG.mdrequire/DialogSystem.php: zwei neue Conditions in choiceAllowed():hasFlag() um System-Flag-Mechanismus erweitert: Flags die automatisch aus DB-Feldern abgeleitet werden ohne manuellen prefs-Eintragcommon.php: GAME_VERSION-Konstante von 0.0.1 auf 0.0.5 aktualisiertpatch.sql: game_version in der settings-Tabelle auf 0.0.5 gesetzt (via INSERT ... ON DUPLICATE KEY UPDATE)bank.php: Komplette Neuentwicklung auf Basis des Dialog-Systemsrequire/Character.php: neue Methoden setGoldInBank(), addGoldInBank(), setSchliessfachStufe(), Getter schliessfachStufe()require/DialogAjax.php: System-Flag has_schliessfach → schliessfachStufe() > 0require/DialogSystem.php:templates/partials/dlg-system.js (v1.3):patches/current/seed_bank.php (neu): Seed für npc_descriptions (Aldric) + dialog_trees (aldric/bank)patches/current/patch.sql: schliessfach_stufe-Spalte + 3 Settings (bank_sf_max_stufe, bank_sf_mieten_kosten, bank_sf_upgrade_kosten)has_schliessfach-Flag false)bank_sf_max_stufe (default 3)schliessfach.php (Inhalts-Verwaltung) ist Platzhalter — folgt in späterem Updateinn.php: CSRF-Schutz für alle zustandsändernden Aktionentraining.php: CSRF-Schutz für alle POST-Formulare und GET-Aktionentemplates/layout/info.php: method_exists-Guards für Output::getBackground() und Output::getBodyClasses() — kein Fatal Error wenn PHP die alte Output.php noch im Speicher hat während ein Patch läufttemplates/layout/interactive.php: dasselbe für Output::getBodyClasses()admin_patch.php: method_exists-Guard für Output::addBodyClass() (Abwärtskompatibilität zu Servern mit älterer Output.php)templates/partials/dlg-system.js: In showNpcResponse — Action-Nodes mit leeren lines überspringen den NPC-Bubble-Aufbau und rufen fetchAction direkt auf (kein leerer Bubble, kein Doppeltext)dialog_trees (aldric/bank): Alle _ja-Action-Nodes (sf_mieten_ja, sf_upgrade_ja, sf_downgrade_ja, sf_aufgeben_ja) haben jetzt "lines": []sf_mieten_ja.loop_to → "sf_menu" (statt "intro") — bypass stale Conditions-Cachesf_aufgeben_ja.loop_to → "sf_aufgeben_tschuss" (neuer Node mit navigate: "bank.php") → Seiten-Reload erzwingt frische Conditions für introcore/charstats_func_inc.php: neue Zeilen nach Runden — 🪙 Gold (gold-formatiert mit Tausenderpunkt) und 💎 Edelsteine (nur sichtbar wenn > 0). Beide mit data-stat Attribut für JS-Live-Update.templates/edahnien.css: .char-divider Trennlinie, .gold-val (gold-bright), .gem-val (hellblau)templates/partials/dlg-system.js: updateCharSidebar() aktualisiert Gold + Gems live nach AJAX-Aktionen (Bank, Inn, etc.) — Gems-Zeile wird bei 0 automatisch ausgeblendettemplates/edahnien.css: .dlg-input-wrap neu strukturiert — Input-Feld und Submit-Button sitzen jetzt in einer .dlg-input-row nebeneinander (ein gemeinsamer gerundeter Rahmen, Feld links, Button rechts). Abbrechen-Button darunter als eigene Zeile. Fehlermeldung direkt unter der Zeile.templates/partials/dlg-system.js: Submit- und Cancel-Button haben nicht mehr choice-btn Klasse (die startet mit opacity:0 + transform und bekam nie .visible → Buttons waren unsichtbar). Neues dlg-input-row-Div als Container für Feld + Submit.require/Character.php: drei neue Methoden gemsInSafe(), setGemsInSafe(), addGemsInSafe() (Getter/Setter für gemssafe-Spalte)bank.php:templates/edahnien.css: Styles für .sf-panel, .sf-panel-head, .sf-title, .sf-gems-display, .sf-bar, .sf-actions, .sf-action-block, .sf-label, .sf-input-row, .sf-input, .sf-btn, .sf-hint, .sf-msgdialog_trees (aldric/bank): Node sf_oeffnen + zugehörige Choice aus sf_menu entfernt — das Schließfach öffnet sich nicht mehr über den Dialog, sondern ist inline auf der Seitepatch.sql: gemssafe INT UNSIGNED NOT NULL DEFAULT 0 + Setting bank_sf_gems_kapazitaet = 10require/DialogSystem.php: neue Vars gems, gemsinsafe, sf_kapazitaet, sf_token in der vars-Maptemplates/partials/dlg-system.js:bank.php: altes sf-panel-HTML + JS-Block entfernt (sfAction(), sfShowMsg(), Panel-Markup) — ersetzt durch Dialog-Widgettemplates/edahnien.css: neue Widget-Styles .sf-widget-bubble, .sf-widget, .sf-widget-head, .sf-widget-title, .sf-widget-gems, .sf-widget-bar, .sf-widget-rows, .sf-widget-row, .sf-w-label, .sf-w-hint, .sf-w-msgdialog_trees (aldric/bank): Node sf_oeffnen als Widget-Node hinzugefügt; in sf_menu neue Choice "Ich will zum Schließfach." → sf_oeffnenfluchkessel.php: neuer AJAX op=fluch_klagen:require/DialogSystem.php: System-Flag has_fluch → Buff::get($character, 'fluch') !== nulltemplates/partials/dlg-system.js: fetchAction unterstützt jetzt res.navigate —dialog_trees (agna/fluchkessel):fluchkessel.php (neu): Heilerin Agna als vollständiger Dialog-NPCrequire/DialogSystem.php:village.php: "Das Lazarett" / lazarett.php → "Der Fluchkessel" / fluchkessel.phppatches/current/seed_fluchkessel.php (neu): Seed für npc_descriptions (Agna), dialog_trees (agna/fluchkessel), settings (4 Kosten-Werte), content_stringspatches/current/patch.sql: 4 neue Settings (fluchkessel_*_kosten)intro → Hauptmenü (7 Optionen)heilen → Kosten zeigen + Bestätigung → heilen_ja (Action) → heilen_donefluch → Beschreibung + Bestätigung → fluch_ja (Action) → fluch_donegift_check_ja/gift_keine_zutat → via has_item/has_item_not für Giftdrüsetraenke_menu → 4 Tränke mit je has_item/has_item_not Conditionsinfo → Lore → info_zutaten → Zutaten-Übersichtgehen → navigate: village.phphas_item: gibt immer false zurück → Choice ausgeblendet (Spieler hat keine Zutaten)has_item_not: gibt immer true zurück (Bedingung erfüllt) → Choice sichtbarsetnewday.php _nd_events(): Wanderhändler-Roll ersetzt altes vendor-Settingmarkt.php:patch.sql: 3 neue Settings wanderhaendler_city, wanderhaendler_date, wanderhaendler_chancemarkt.php (Umbau): City-aware Navigations-Hub + NPC-Dialog-Integrationpatches/current/seed_markt.php (neu): Seed für npc_descriptions (bela) + dialog_trees (bela/markt)setnewday.php _nd_events(): $validCities auf Rassendörfer aktualisiertrequire/DialogSystem.php: render() in build() + render() refaktoriertrequire/SceneHotspots.php: load() liest zusätzlich dialog_npc und dialog_context aus DB; beide Felder im return-Arraymarkt.php:templates/layout/scene.php:scene_hotspots: neue Spalten dialog_npc VARCHAR(60) + dialog_context VARCHAR(60)core/Router.php allowVariants(): speichert jetzt auch die rohe URL ohne Counter in $session['allowednavs'], zusätzlich zur bisherigen Counter-Versiontemplates/layout/scene.php: doppeltes class=-Attribut auf <nav> behoben — scene-nav--fallback wurde wegen zwei class=-Attributen nie gesetzt, mobile JS fand das Element nichtadmin_hotspots.php (neu): visueller Drag-Editor für Scene-Hotspots — Superuser-Level 2+scene_hotspots-Tabelle: page_key + sort_order als UNIQUE KEY, Spalten x/y/w/h als DECIMAL(5,2)markt.php: Hotspots nicht mehr hardcodiert — werden aus scene_hotspots (page_key markt:schauen:edahnien) geladen; try/catch-Fallback (leere Liste) wenn Tabelle fehltadmin_grotte.php: „Hotspot-Editor"-Link in der Welt-&-Rassen-Sektion (Level 2+)templates/layout/scene.php: ?hsdebug=1-Debug-Modus vollständig entfernt (CSS, HTML, JS)templates/layout/partials/game_topbar.php (neu): Header-Partial aus info.php extrahiert — <header class="info-topbar"> inkl. Logo-Fallback, zentrierter Seitentitel, Icon-Buttons (Post/Profil/Wechseln/Hilfe/Logout) und $subtitle-Zeiletemplates/layout/info.php: eigener Header-Block durch <?php include __DIR__ . '/partials/game_topbar.php'; ?> ersetzttemplates/layout/scene.php: benutzerdefinierter <header class="scene-topbar"> entfernt; .scene-topbar-CSS-Block entfernt; stattdessen game_topbar.php-Include; position:fixed + z-index für info-topbar via inline <style> gesetztpatches/current/seed.php: drei neue Dialog-Bäume + npc_descriptions ergänztscene_pages.show_nav_links TINYINT(1) NOT NULL DEFAULT 0 — neues Feld pro Szenerequire/SceneHotspots.php: neue Methode sceneConfig(string $pageKey): array — lädt das Flagmarkt.php: SceneHotspots::sceneConfig() aufrufen, $GLOBALS['sceneShowNavLinks'] setzentemplates/layout/scene.php:admin_hotspots.php:patch.sql: ALTER TABLE scene_pages ADD COLUMN IF NOT EXISTS show_nav_links …admin_hotspots.php:patch.sql: ON DUPLICATE KEY UPDATE für scene_hotspots aktualisiert nur label + url — Koordinaten (x/y/w/h) werden nicht überschrieben, damit Admin-Editor-Anpassungen erhalten bleiben. Einmalige UPDATE-Statements korrigieren DB-Zeilen die noch Platzhalter-Koordinaten haben.patch.sql: Kalibrierte Startkoordinaten für alle 5 Markt-Stände eingetragen (aus Admin-Editor übernommen)admin_hotspots.php: Neue „Vorschau-Bild"-Sektion im Side-Panel (nach der Szenen-Trennlinie, vor der Hotspot-Liste)!important-Regeln aus der no-bg-Fallback-CSS entfernt (damit style.background aus JS greifen kann)fluchkessel.php op=fluch_entfernen: AJAX-Antwort liefert jetzt vfx + sidebar_buffstemplates/partials/dlg-system.js: In showNpcResponse wurde appendDialogRow ans DOM gehängt *bevor* geprüft wurde ob node.lines leer ist. Folge: unsichtbarer leerer Row blieb im DOM, dann erschien die echte AJAX-Antwort als zweiter Row. Fix: appendDialogRow wird erst nach dem Empty-Check aufgerufen. Widget-Nodes unverändert (benötigen die Row auch ohne Text).patches/current/seed_fluchkessel.php: Dialog-Baum agna/fluchkessel um fehlende Nodes erweitert:patches/current/patch.sql: NPC-Beschreibungen für alle 5 Marktstände (+ Dox) korrigiertburgschmiede.php (Neuentwicklung): ersetzt alten Platzhalter-Code vollständigrequire/DialogSystem.php: neues System-Flag rassen_aufschlag in hasFlag() — true für Elf(2), Dunkelelf(4), Ork(6), Fee(7), Halb-Elf(8)templates/partials/dlg-system.js: showChoices() wendet applyVars() auf ch.text und ch.cost an — {upgrade_kosten} erscheint korrekt in Choice-Text und Preis-Labelpatches/current/seed.php: Theron npc_description + dialog_trees (theron/schmiede)patches/current/patch.sql: Setting schmiede_upgrade_kosten = 500 (Basispreis vor Rassen-Modifier)intro → verbessern_check_waffe / verbessern_check_ruestung / keine_blaupause / rohstoffe / info_blaupausen / preis_erklaerung / gehenverbessern_check_waffe → verbessern_ja_waffe (Action → loop_to verbessern_done) | intro | bereits_verbessertverbessern_check_ruestung → verbessern_ja_ruestung (Action → loop_to verbessern_done) | intro | bereits_verbessertrequire/Character.php: neue Methoden addBattlepoints(int $n) + addReputation(int $n) (additive Setter mit min-0-Guard)require/DialogSystem.php:templates/partials/dlg-system.js:templates/edahnien.css: neue Styles .rl-widget, .rl-head, .rl-table, .rl-rank/.rl-name/.rl-lv/.rl-bp, .rl-self, .rl-sep, .rl-emptypatches/current/seed.php: Drek-Dialog aktualisiertpatch.sql: ALTER TABLE characters ADD COLUMN IF NOT EXISTS battlepoints INT UNSIGNED NOT NULL DEFAULT 0arena.php op=ring: Hintergrundbild {city}_arena_in.png — Perspektive aus dem Ring (Kampfhintergrund für später)arena.php op=tribuene: Hintergrundbild dynamisch — leer oder voll je nach arena_kampf_aktiv-Settingpatch.sql: Setting arena_kampf_aktiv = 0 (Initial-Wert; wird später vom Kampfsystem geschaltet)arena.php op=kerker (Hub): von Output::render('info') auf Output::render('scene') umgestelltpatch.sql: scene_pages für arena:kerker:edahnien (show_nav_links=1) + 6 scene_hotspots Einträge mit Platzhalter-Koordinaten (2×3 Grid)arena.php (Neuentwicklung): ersetzt alten Platzhalter vollständigpvparena.php (Redirect-Stub): leitet alle Requests transparent auf arena.php weiter; AJAX liefert JSON-Fehler mit Hinweis zum Neu-Ladenvillage.php: Nav-Link von pvparena.php auf arena.php umgestelltpatches/current/seed.php: 7 NPC-Descriptions (Drek + 6 Gladiatoren) + 7 Dialog-Bäumegauntlet_heute-Flag: setnewday.php muss prefs['flags']['gauntlet_heute'] täglich löschenavatar_path leer (Dateien fehlen noch)inn.php: alle Mara-Logik aus mara.php integriertmara.php: Redirect-Stub (leitet alle ops auf inn.php?op= weiter; AJAX liefert JSON-Fehler mit Hinweis zum Neu-Laden)patches/current/seed.php: mara/zimmer-Dialog — alle Action/Navigate-URLs von mara.php?op= auf inn.php?op= umgestelltpatch.sql: ALTER TABLE settings MODIFY COLUMN setting VARCHAR(64) NOT NULLburgviertel.php: enthält jetzt beide NPCs — Siegbert (Grundstückskauf) und Hannes (Bautrupp)bautrupp.php: nur noch Redirect auf burgviertel.php (für alte Links/Bookmarks)docs/dialog_hannes_bautrupp.json: alle action-URLs von bautrupp.php?op= auf burgviertel.php?op= geändertdocs/dialog_siegbert_burgviertel.json: goto_bautrupp-Node — navigate: bautrupp.php entfernt, stattdessen terminal: true (Hannes steht jetzt auf gleicher Seite)houses.php: alle bautrupp.php-Referenzen auf burgviertel.php aktualisiert (Redirect, Router::allow, Link)seed_burgviertel.php über Admin-Patch → Seeds einspielen ausführen (aktualisiert dialog_trees für siegbert/burgviertel + hannes/bautrupp)burgviertel.php (kein op): nur Siegbert, Bild edahnien_burgviertel.pngburgviertel.php?op=zimmererei: Hannes/Zimmererei, Bild edahnien_burgviertel_bautrupp.pngop=holzbalken_kaufen&qty=5|10|20 — nutzt Item::shopBuy()holzbalken_preis aus item_definitions.price_golddialog_hannes_zimmererei.json: Holzbalken-Shop-Nodes + alle Bautrupp-Nodes, context zimmerereigoto_bautrupp: jetzt navigate: burgviertel.php?op=zimmerereibautrupp.php: Redirect auf burgviertel.php?op=zimmerereihouses.php: kein Haus → burgviertel.php (Siegbert), Bautrupp-Link/Plot-Redirect → burgviertel.php?op=zimmererei, allowPrefix statt allow für burgviertelseed_burgviertel.php: lädt jetzt dialog_hannes_zimmererei.json mit context hannes/zimmerereiseed_burgviertel.php via Admin-Patch → Seeds einspielen ausführenItem::renderPopupHtml() komplett neu: erzeugt jetzt das vollständige .iv-window.iv-D HTMLtemplates/layout/info.php: Google Fonts import (Cinzel, Cormorant Garamond, Cormorant SC, Inter)templates/edahnien.css: Großer neuer Abschnitt Inventar/Rucksack Redesign (Variant D) —admin_patch.php: neues op=pack_self — erstellt ZIP mit NUR admin_patch.php + manifest.json (type: self_update); früher Exit wie op=packbuildSelfUpdateZip(): neue Hilfsfunktion für den Self-Update-ZIP-AufbaubuildPatchZip(): admin_patch.php wird automatisch aus normalem Patch-ZIP ausgeschlossen (wie dbconnect.php)op=''): Wenn admin_patch.php in files.txt → blaue Info-Box mit direktem "Self-Update-Paket erstellen"-Buttonop=upload: erkennt manifest.type=self_update → kein GAME_VERSION-Update, eigene Erfolgsmeldung "Patch-Manager aktualisiert"core/Output.php: neue Methoden addBodyClass(string) / getBodyClasses() — beliebige CSS-Klassen an <body> setzentemplates/layout/info.php + interactive.php: <body class="..."> aus Output::getBodyClasses()admin_grotte.php, admin_patch.php, admin_races.php, npc_editor.php, dialog_editor.php: Output::addBodyClass('no-vfx') nach Zugangsschutztemplates/edahnien.css: VFX-Selector auf body:not(.no-vfx).blur-* eingeschränkt; .char-buff-row[data-tooltip]::after CSS-Tooltip (rechts unterm Buff, fade-in on hover)core/charstats_func_inc.php: data-tooltip="Angriff +1 · bis Tagesende" auf .char-buff-row — Stat-Zeilen + Ablauf-Infoinn.php AJAX: sidebar_buffs-Einträge bekommen tooltip-Feldtemplates/partials/dlg-system.js: updateCharSidebar() setzt data-tooltip auf dynamisch erzeugten Buff-Zeilentemplates/partials/dlg-system.js: Neues action-Property für Dialog-Nodes (AJAX-Call statt Navigate), loop_to-Property startet Dialog nach Aktion bei neuem Node neu; dlgResetTo() intern + window.dlgRestartAt() extern; fetchAction() mit Buff-Info-Block und Tod-Redirect; updateCharSidebar() aktualisiert .char-card Buff-Section + Body-VFX-Klassen live nach AJAX-Aktioninn.php: AJAX-Endpoint op=ale&ajax=1 (JSON-Response mit Flavor, Buff-Info, Tod-Flag, sidebar_buffs, vfx, gold); Nav-Link-Interception via inline JS + Toast-Banner bleibt auf Seitetemplates/edahnien.css: .inn-toast Styles (top-center, fade-in/out, error-Variante)docs/seed_dialog_caspar.php: ale_ja-Node nutzt jetzt action+loop_to statt navigate; neuer nach_kauf-Node ("Was noch?" mit reduzierten Choices)content_strings inn.php textid=20 bereinigt (Caspar-Sprechtext entfernt, Dialog übernimmt das)require/Rewards.php NEU: Globales Balance-System für Belohnungen (EXP, Gold, Turns, Gems) aus settings-Tabelledbwrapper.php: Rewards.php global eingebundenrequire/Character.php: neue Methoden addExperience(), addGems(), Kampfkunst-Getter/Setter (kunstStufe, kunstExpInvested, meisterkampfStufe, meisterkampfCooldown)training.php NEU: Ausbildungslager Hub-Seite (3 Bereiche + Charakter-Übersicht)training_basic.php NEU: Trainingsplatz (Turns → EXP, tägliches Limit via boughttrain)training_skill.php NEU: Ausbildungsraum (EXP investieren → Kampfkunst-Stufe 1–4)training_master.php NEU: Meisterkampf (Meilenstein-Prüfungen, Cooldown, Belohnungen)village.php: "Ausbildung"-Navigation auf training.php konsolidiert (train.php/Dojo/garrisons entfernt), neue Seiten whitelistednpc_editor.php NEU: Admin-Tool zum Verwalten von NPC-Beschreibungen (Superuser ≥ 4, in admin_grotte.php eingebunden)admin_grotte.php: Legacy-Links (superuser.php 2008, houses.php fehlend, hilfe.php 2008) entfernt; neue Sektion "System & KI" mit NPC-Editor und Patch-Verwaltungnpc_descriptions angelegt, 9 Ausbilder-NPCs als Seed eingespielt (Krom, Helga, Caelindor, Dolgrak, Zael, Peregrin, Skarra, Tiri, Aerin)race-Tabelle durch neue races-Tabelle ersetzt (JSON-Felder für Klassen, Varianten, Boni)require/Race.php komplett neu geschrieben für die neue Tabellenstrukturrequire/WizardHelper.php: RACES + RACE_KLASSEN Konstanten entfernt, neue loadRaces() Methode aus DBwizard.php: Rassen und Klassen-Beschränkungen dynamisch aus DB, Varianten-Cycling (‹ › für Avatar-Varianten)admin_races.php: neuer Rassen-Editor (Superuser ≥ 2)admin_grotte.php: Link zum Rassen-Editor hinzugefügtrequire/Character.php: neuer Getter appearanceVariant()characters.appearance_variant TINYINT DEFAULT 0 Spalte hinzugefügtrequire/DialogSystem.php NEU: DialogSystem::render(npcSlug, context, character) — lädt Baum aus dialog_trees, NPC-Info aus npc_descriptions, filtert Choices nach Conditions (flag/flag_not/level_min), gibt Widget-HTML + einmalig dlg-system.js inline ausdialog.php NEU: POST-Handler — CSRF-Check, Effekte (set_flag, give_exp, level_up-Platzhalter), Redirecttemplates/partials/dlg-system.js NEU: JS-Engine (dlgInit, dlgReset, Typewriter, Slot-System row-a→row-p→row-b, Choice-Handling, Form-Submit für Terminal-Nodes)templates/edahnien.css: Bangers-Font im @import + kompletter .dlg-*-CSS-Blockdialog_editor.php NEU: Admin-Editor für dialog_trees (Liste, Bearbeiten, Neu, Toggle, Löschen) — JSON-Textarea mit Syntax-Hinweisen + Template für neue Einträgeadmin_grotte.php: Link zu Dialog-Editor unter "System & KI" (mit Tooltip-Labels zur Unterscheidung von NPC-Editor)CLAUDE.md: vollständige Dokumentation beider NPC-Systeme (KI vs. Skript) mit JSON-Format, Effekten, Conditions, Verwendungtraining.php Hub: 3 Stationslinks in der Sidebar-Navigation (Trainingsplatz, Ausbildungsraum, Meisterkampf) — doppelt zu den visuellen Karten, für konsistente Navigationtraining.php Hub: Comic-Stil NPC-Dialog zwischen Intro-Text und Stationskartentemplates/partials/dlg-system.js: cycle-Property auf Choices — rotiert durch Node-Liste pro Klick, Reset bei dlgReset(); Typewriter-Fix (Bubble-Größe via unsichtbarem Vortext); Auto-Scroll der Stage; navigateTo() mit sessionStorage-Flag für Seiten-Übergangsblendetemplates/edahnien.css: Dialog-Redesign (nahtlos, eckige Portraits 92×116px, dunkle Bubbles, dezente Scrollbar); ↺-Reset-Icon oben rechts; #page-veil entfernt (dynamisch per JS)require/DialogSystem.php: can_level_up / can_not_level_up Conditions (Platzhalter-Formel level*(level+1)*50); navigate-URLs automatisch in Router-Whitelist; ↺-Reset-Icon ins HTMLtemplates/layout/info.php + interactive.php: Seiten-Übergangsblende per JS (nur bei dlg-nav sessionStorage-Flag)patches/current/seed.php: Krom begruessung-Dialog (Trainingsplatz/Ausbildungsraum/Meisterkampf mit XP-Check, Nochmal-Cycle 3 Varianten, Gehen)training.php: TRAINING_ACTIONS_DISABLED = true — Trainings-/Kauf-/Kampfbuttons ausgeblendet bis Kampfsystem integriert; Stats/Anforderungen bleiben sichtbar; auf false setzen zum Aktivierentraining.php basic/skill/master: Hintergrundbild aus templates/assets/poi/{city}_ausbildungslager.png (wie Hub)training.php basic/skill/master: Output::render('interactive') → 'info' (alle Exits)$title für jede Sub-Seite gesetzt (Trainingsplatz / Ausbildungsraum / Meisterkampf)training.php: Die drei Karten (Trainingsplatz / Ausbildungsraum / Meisterkampf) im Content-Bereich entfernt — Navigation läuft jetzt vollständig über den Krom-Dialog. Sidebar-Links bleiben. Alter .npc-dialog-* CSS-Block entfernt (wurde durch DialogSystem ersetzt).templates/partials/dlg-system.js: navigateTo() erzeugt keinen wiederverwendeten Veil mehr (kein getElementById), stattdessen immer frisches Element; pageshow-Listener entfernt Veil wenn Seite aus bfcache wiederhergestellt wirdtemplates/layout/info.php + interactive.php: Veil-Script in IIFE, pageshow-Guard entfernt Veil bei bfcache-Restore der Zielseiteinn.php komplett neu: DialogSystem::render('caspar','schenke'), Hintergrundbild, op-Handler (ale/essen/zimmer/geruechte/mara), Schankgespräche erhalteninn.php Hub: Sidebar-Nav mit Heading "Angebote" + 5 Aktions-Links (Ale/Essen/Gerüchte/Zimmer/Mara) parallel zum Dialogpatches/current/seed.php: Caspar + Mara in npc_descriptions + caspar/schenke Dialog-Baum (vollständiger Fahrplan)docs/seed_dialog_caspar.php: standalone Seed-Skript (ersetzt docs/seed_dialog_mara.php)common.php: seed_dialog_caspar.php in $allownonnavcost-Feld im Dialog-JSON → rechts ausgerichtet in der Choice-Button-Zeileinn_essen_heute (TODO: in newday.php zurücksetzen)inn.php: Gold-Check für alle kostenpflichtigen Aktionen; Betrug-Option bei fehlendem Goldinn_ale_cost), bestehende Buff-Logik + Betrug möglichinn_essen_cost); Buff tagesgericht (ATT+inn_essen_att_bonus, MaxHP+inn_essen_maxhp_bonus für inn_essen_turns Kämpfe); Tagesflag inn_essen_heuteinn_zimmer_cost); setzt Flag inn_zimmer_sicher + Logout-Link; am Folgetag via newday.php: +inn_zimmer_extra_turns Runden + "Gut geschlafen"-Meldung?betrug=1): Erfolg = Aktion kostenlos; Misserfolg = Gold weg (min vorhandenes, max Kosten) + Buff "Blaues Auge" (IQ-1, inn_betrug_blaues_auge_turns Kämpfe) + permanenter Charismaverlust (inn_betrug_charm_loss)inn_betrug_base_chance + Schurke-Bonus (inn_betrug_dieb_bonus) + Charisma × inn_betrug_charm_per_point (max 95 %)require/Character.php: charisma() Getter + setCharisma() Setternewday.php (5b): inn_essen_heute-Flag zurücksetzen; Zimmer-Bonus anwenden (falls inn_zimmer_sicher gesetzt); "Gut geschlafen"-Ausgabecore/charstats_func_inc.php: maxhp + Buff::statBonus($char, 'maxhp') (Essen-Buff sichtbar in LP-Leiste)characters.charisma INT DEFAULT 0; 13 neue Balance-Settings für inn_*require/Buff.php NEU: Vollständiges Buff-System (kein Stacking, Slug-eindeutig per Charakter)require/Character.php: id() Alias für acctid() (wird von Buff genutzt)dbwrapper.php: require_once 'require/Buff.php' hinzugefügtinn.php Ale-Op: Staged Buff-Anwendung via Buff::drinkAle(), Tod bei Stage 5 (Redirect → shades.php), Flavor-Text + Buff-Info je nach Stagenewday.php: Buff-Verwaltung nach Revival (Block 4b): getAleStage (vor Remove), removeNewdayBuffs, remove ale_rausch, cleanup, applyKater wenn Stage ≥ 3; bei Tod via Revival: removeAllcore/charstats_func_inc.php: Alte $session['bufflist'] durch Buff::statBonus() ersetzt; Buff-Anzeige-Block (Status-Sektion mit Label + Richtungspfeil); VFX-JS (document.body.classList.add(vfxClass))templates/edahnien.css: .char-section-title, .char-buff-row, .buff-label; body.blur-leicht und body.blur-stark (filter: blur auf content-panel, info-content, dlg-wrap)character_buffs Tabelle (character_id, slug UNIQUE, label, stage, stats JSON, vfx, expires_type ENUM, expires_at, invisible, data JSON)admin_patch.php: seed.php wird parallel zu patch.sql in ZIP eingebunden (buildPatchZip()), bei op=run_sql nach SQL automatisch ausgeführt, bei op=upload aus ZIP extrahiert und ausgeführt; Übersicht zeigt seed.php an wenn vorhandenpatches/current/seed.php NEU: Krom/begruessung Dialog-Seed (ersetzt den SQL-String-Ansatz; sicher mit json_encode via PDO)patches/vX.Y.Z/patch.sql wird beim nächsten Request automatisch eingespielt — kein manuelles install.php mehr nötigpatch_log-Tabelle: verfolgt welche Patches bereits auf dem Server eingespielt wurdencharacters.patch_seen: merkt pro Spieler welche Version er zuletzt gesehen hatinstall.php – Patch-SQL-Step: beliebige patches/vX.Y.Z/patch.sql direkt aus dem Browser einspielenrequire/Database.php: SHOW COLUMNS wurde nicht als result-liefernde Query erkannt ('SHOW ' stimmte nie mit 'SHOW C' überein) → getDbColumns() gab immer [] zurück → saveAll() schrieb nichts → Badnav nach jedem Login. Fix: str_starts_with($verb, 'SHOW')acclogin.php: labyrinthturns in $session['user2'] existiert nicht in der DB → saveAll() warf PDOException → allowednavs nie geschrieben → Badnavrequire/Character.php: save() / saveAll() filtern jetzt unbekannte Felder via getDbColumns() (gecachter SHOW COLUMNS)newday.php: hashorse, fedmount mit ?? 0 abgesichert (PHP 8 undefined key Warning)require/Character.php: $kleidung['haare']++ / ['naegel']++ auf undefined key → Warning. Fix: min(40, (... ?? 0) + 1)lib/kika_func_inc.php: sextoday, avatar, bestaetigt mit ?? abgesichertlogin.php: Logout leert jetzt vollständig via $_SESSION = [] + session_destroy() statt nur $session = []register.php: Usernamen dürfen jetzt Ziffern enthalten; Catch auf \Throwable erweitertonclick-Handler: json_encode() in Inline-Attributen erzeugte doppelt-gequotete Strings → Kampfkunst/Farben ließen sich nicht auswählen. Fix: einfache Anführungszeichenuser-Tabelle auf Live-Server fehlte → Registrierung schlug immer fehl. Tabelle + Migration in patch.sqlaccounts, accounts2, accounts_bio neu erstellt (referenzierten nach Aufräumarbeiten ungültige Spalten → General error: 1356)newday.php: $session['user2']['block'] wurde fälschlicherweise als leeres Array [] gesetzt. Unter MariaDB strict mode warf dies beim Schreiben in die TINYINT(1)-Spalte characters.block eine PDOException, die saveAll() zum Schweigen brachte — allowednavs wurde nie gespeichert, alle Nav-Links nach newday.php führten in Badnav.common.php: Schutzcheck im user2→user Merge: Array-Werte überschreiben keine skalaren DB-Felder mehr.patches/, CHANGELOG.md, deploy.bat