2 Helden online · 3 registriert
TAG 128 · DER DRACHE SCHLÄFT NICHT

Erlege den Grünen Drachen.

Edahnien ist ein Reich am Rande des Vergessens. Der Drache hat sich tief im verfluchten Wald eingenistet — und 0 Helden haben es bisher nicht zurück geschafft. Vielleicht bist du der nächste. Vielleicht der letzte.

3
Registrierte Helden
0
Gefallene Seelen
12 000
Gold Belohnung
Impressum v0.2.7

Feedback & Bugreport

Changelog

[0.2.7] – 2026-06-01

Fix: gamedata.sql/schema.sql laufen non-strict — verhindert Deploy-Crash bei ungültigen ENUM-Werten (require/MasterBuilder.php)

Was

Warum

[0.2.6] – 2026-06-01

Fix: Fang-Rückkehr dynamisch an den Kampf-Ursprung (combat.php)

Fix: Fang-Minispiel — Abbrechen-Button + Wizard-Schrittzähler (fangen.php)

  • $wizBackUrl = 'combat.php' → „Abbrechen" führt zurück in den (noch aktiven) Kampf. combat.php ist
  • Nach erfolgreichem Fang blendet das JS den Back-Button (#wiz-back-btn) aus — kein Zurück mehr.
  • „Schritt X/6"-Zähler + Fortschritts-Punkte + „Heldenerschaffung"-Eyebrow per CSS ausgeblendet

Refinement: Fang-Meilenstein nutzt jetzt das Standard-Meilenstein-Overlay (fangen.php)

Bugfix: „Du hast kein Fanggeschirr" trotz Quest-Fangausrüstung — Slug-Mismatch (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.

Change: Fangausrüstung wird beim Klick auf „Fangen" verbraucht (fangen.php, fangen_ajax.php)

  • Seed seed_milestone_erstes_tier_20260531.php: Milestone-Definition erstes_tier_gefangen
  • fangen_ajax.php (Erfolg): Milestone::award($character, 'erstes_tier_gefangen') — gibt nur beim
  • fangen.php: eigenes Vollbild-Overlay #fang-overlay (self-contained, da fangen.php

P0-Bugfix: Fatal beim Fangen — $session überschrieb die globale Session-Referenz (fangen.php, fangen_ajax.php, ajax/combat_ajax.php)

Fix: Quest „Der Jäger und das Biest" (alm_t8b_jaeger) — Endlosschleife behoben (combat.php + Seed)

  • kein 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 neutrale
  • npc_hagen/kampf_sieg: EIN Sieg-Bark-Node → navigate_js: combatResumeFromDialog() → schließt

Fix: navigate_js + effects ohne terminal:true — Effects wurden nie gesendet (dlg-system.js)

Bugfix: NPC-Partner verschwand nach „Weiter kämpfen" (combat.php)

Feature: ?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 in

Feature: No-Kill-Trigger für Fang-Quests — Monster unkillbar (creature_definitions.no_kill_flag)

  • Schema: creature_definitions.no_kill_flag VARCHAR(100). Ist gesetzt + der Spieler hat dieses
  • combat.php: leitet $creature['no_kill'] aus no_kill_flag + Spieler-Flag ab (vor Session-Start);
  • CombatSession::start: persistiert den no_kill-Marker in der enemies-JSON (wie meisterkampf).
  • CombatService::runRound: $enemyHpFloor = no_kill ? 1 : 0, durchgereicht in alle drei
  • UI: rotes Warn-Badge „⚠ Lebend fangen" + COMBAT_STATE.creature.no_kill. Da die HP auf 1 fällt
  • Seed: seed_bergluchs_no_kill_20260531.php setzt bergluchs_riese.no_kill_flag = 'bergluchs_lauert'

Fix: Choice-Button bleibt nach Klick sichtbar (dlg-system.js)

Fix: navigate_js in autoTerminal() (dlg-system.js)

Bugfix: $t4Done prüfte toten Quest-Slug alm_t4_kellerproblem (almhausen.php)

Bugfix: Dialog-Effects laufen nur bei 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-Dialog-Sub-Nodes (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:true
  • dorfplatz.nach_sieg: + terminal:true
  • almhaenge.etappe1_spur / etappe2_pranken / etappe3_falle / etappe3_falle_nochmal / abschied_zum_dorf: + terminal:true
  • kampf_sieg-Sub-Nodes (sieg_etappe1_done, sieg_etappe2_done, sieg_bergluchs_getoetet, sieg_generic): + terminal:true
  • kampf_start + kampf_hp50 Sub-Nodes: + terminal:true (für Konsistenz + sicheren autoTerminal-Close)

Feature: Fangen erst ab Gegner-HP < 50 % (combat.php, fangen.php, templates/css/combat.css)

Feature: Hagen ruft „JETZT WERFEN!" bei <50 % Gegner-HP (seed_npc_hagen_kampf_hp50_20260531.php)

Feature: Hagen-Bark beim Kampf-Start mit Fangen-Erklärung (seed_npc_hagen_kampf_start_20260531.php)

Feature: Stage-Flags via Kampf-Sieg-Dialog statt Klick (seed_fix_alm_t8b_kampfsieg_flags_20260531.php)

Tooling: tools/gen_image.py — Bildgenerierung über Gemini API (Nano Banana)

Bugfix: Hagen begleitete nur den Boss-Kampf, nicht die zwei Vor-Etappen (seed_fix_npc_hagen_trigger_etappe_20260531.php)

Bugfix: NPC-Partner verschwand nach „Weiter" (Companion Phase C)

  • Schema (patch.sql): 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).
  • Ortsgebunden: 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).
  • Seeds: seed_fix_npc_hagen_trigger_flag_20260531.php (Flag + Stopp-Flag + Ort).
  • Quest-KI: Quest muss 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-KI: Hagen-Trigger umgesetzt — Dorfplatz + Almhänge (almhausen.php)

  • Dorfplatz (Default-Branch): Zwei-Phasen-Logik — $hagenPre (T-8 completed, T-8b weder
  • Almberg (op=almberg): DialogSystem::render('hagen_streicher', 'almhaenge', $character)

Bugfix: Marta-Choice „Fangausrüstung abholen" blieb nach Abholen sichtbar (seed_fix_marta_fangausruestung_navigate_20260531.php)

Story-Umbau: Hagen verschwindet vom Dorfplatz, wartet am Almhang (seed_fix_alm_t8b_hagen_almhaenge_split_20260531.php)

  • Hagen-Dorfplatz-Tree (hagen_streicher/dorfplatz) gekürzt auf:
  • Hagen-Almhänge-Tree (hagen_streicher/almhaenge) neu angelegt (active=1):
  • Dorfplatz: render hagen_streicher/dorfplatz NUR wenn:
  • Almberg-Page (almhausen.php?op=almberg): render hagen_streicher/almhaenge wenn quest_active alm_t8b_jaeger + has_item jaeger_ausruestung + flag_not bergluchs_riese_gefangen.

Textfix: „Hagens Spezialausrüstung" → „Fangausrüstung" durchgängig (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"
  • Hagen-Dialog: 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 umtextet
  • Marta-Dialog: Choice „Hagens Ausrüstung abholen" → „Die Fangausrüstung abholen"; Übergabe-Node „das hier" → „die Fangausrüstung"

Feature: NPC-Profil hagen_streicher mit Portrait (seed_npc_hagen_portrait_20260531.php)

  • npc_slug = hagen_streicher
  • name = „Hagen Streicher"
  • location_slug = almhausen_dorfplatz
  • avatar_path = characters/npcs/hagen_portrait.webp
  • ai_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 ist

Umbau: alm_t8b_jaeger — 3-Etappen-Jagd mit NPC-Begleiter Hagen (seed_fix_alm_t8b_jaeger_etappen_20260531.php)

Fix: MasterBuilder GAMEDATA_TABLES — 7 Combat-Content-Tabellen wurden NIE synchronisiert (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).
  • schema.sql angewandt (834 OK, 0 Fehler) → 7 fehlende Spalten ergänzt.
  • gamedata.sql v2 (mit Content-Tabellen) non-strict eingespielt (71 OK, 0 Fehler) → Content-Tabellen bit-identisch zu lokal, raeuberhauptmann.special_loot_pool gesetzt, klara_frost-NPC da.
  • quest_definitions wiederhergestellt (war leer), alm_zimmermiete_schulden.category '''side' auf Live + lokal.
  • 8 ursprünglich fehlgeschlagene Seeds force-rerun → alle OK.
  • todos/quest_zimmermiete_category_leer.md — Quest-KI: Korrektur-Seed für die ''-category (Quelle in archivierten Seeds, eingefroren).
  • Verbesserungs-Idee (separat): SeedRunner sollte interne DB-Fehler erkennen statt blind „success" zu melden (Punkt 3) — sonst bleiben solche Schäden unsichtbar.
  • Deploy-Disziplin (Korrektur): Der Projektleiter macht alles richtig (Release → ZIP runterladen → op=upload). Mein erster Verdacht „Deploy übersprang den Schema-Schritt" war FALSCH — das ZIP enthielt schema.sql/gamedata.sql gar nicht (buildPatchZip-Bug oben). Mit dem Fix sind beide ab dem nächsten Release im ZIP.

Fix: buildPatchZip packt schema.sql + gamedata.sql jetzt wirklich (admin_patch.php)

Kampf-Dialog-System — KOMPLETT über das Dialog-System (keine eigene Tabelle)

  • on_start → kurz nach Kampfbeginn
  • enemy_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)

[0.2.5b] – 2026-05-31

Hagen Streicher — Dorfplatz-Trigger für Begleiter-Quest T-8b (almhausen.php)

  • Bedingung: alm_t8_kampfkunst completed UND alm_t8b_jaeger NICHT completed.
  • DialogSystem::render('hagen_streicher', 'dorfplatz', $character) (kein Takeover →
  • Kein separater Almhänge-POI nötig: der Dialog navigiert selbst auf

[0.2.5] – 2026-05-31

UX: „+0 Gold" im Siegbildschirm ausblenden

Umbau: alm_t8b_jaeger — 3-Etappen-Jagd mit NPC-Begleiter Hagen (seed_fix_alm_t8b_jaeger_etappen_20260531.php)

~~Umbau: 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_gefangenalm_t8b_bergluchs_gefangen (quest-spezifischer Flagname — verhindert dass ein vor-Quest-Fang die Quest sofort als erfüllt markiert)
  • Description: „Riesen-Bergluchs" → „alter, narbiger Bergluchs"
  • Hagen quest_angebot + alle Lines: „Riesen-Bergluchs"/„mannsgroß" → „alt"/„narbig"/„schwer wie ein Mann"
  • Hagen almhaenge_los.navigate: combat.php?type=quest&slug=bergluchs_riesecombat.php?type=quest&slug=berg_luchs
  • Intro-Choice: „Riesen-Bergluchs" → „Bergluchs"

Umbau: alm_t8b_jaeger — Fang-Quest statt Kill-Quest (seed_fix_alm_t8b_jaeger_fangen_20260531.php)

  • Description: erzählt vom Fangen lebend (Hagen ist Jäger, will das Tier lebend; Hagen lehrt das Fangen)
  • Objective 2: kill: bergluchs_riese × 1flag: bergluchs_riese_gefangen
  • Title bleibt („Der Jäger und das Biest"), Rewards bleiben
  • quest_angebot Lines: „Lebend brauch ich ihn." — Köder, Wurfnetz, Beruhigungsmittel statt Schwert
  • almhaenge_los navigate bleibt auf combat.php?type=quest&slug=bergluchs_riesefangen.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ßen
  • jaeger_ausruestung bleibt narratives Quest-Item ohne eigene Mechanik (der Fang läuft über Companion Phase B mit normalem fanggeschirr aus der Ausrüstung)

Feature: Quest alm_t8b_jaeger — Hagen Streicher und der Riesen-Bergluchs (seed_quest_alm_t8b_jaeger_20260531.php)

  • alm_t9_ernte.prerequisite: alm_t8_kampfkunstalm_t8b_jaeger
  • Klara t8_nach_sieg: give_quest:alm_t9_erntegive_quest:alm_t8b_jaeger
  • Helga helga_t9 Annahme-Cond: quest_done auf alm_t8b_jaeger
  • Hagen nach_sieg gibt T-9 nach Bossfight-Sieg

Feature: Getriggerte feste Begegnungen (Quest-Boss im Wald)

  • Schema (patch.sql): creature_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).
  • Quest steuert nur über Flags: Flag setzen → Gegner erscheint beim Jagen; blocked_flag (z. B. <slug>_gefangen, das fangen_ajax ohnehin setzt) stoppt ihn.
  • Demo (seed_forced_encounter_bergluchs_20260531.php): bergluchs_riese @ almberg_almhausen, Flag bergluchs_lauert. Getestet (ohne Flag → kein forced; mit Flag → garantiert; + gefangen → gestoppt).
  • Doku: docs/forced_encounters.md (für Quest-KI).

Balance: Begleiter-/NPC-Werte auf Spieler-Ökonomie skaliert

Feature: Companion-System Phase C (Begleiter & NPC kämpfen mit)

  • Schema (patch.sql): combat_sessions.ally (JSON) + companion_definitions.is_npc_ally + ability_unlock_level.
  • CombatSession: Ally-State (start($ally), ally(), updateAlly(), Persistenz). clear() schreibt bei source=companion HP/Ohnmacht zurück + zählt den Kampf (Training); npc transient.
  • Companion.php: allyFromCompanion / allyFromNpc, unlockedAbility (Freischaltung ab ability_unlock_level), npcDefinitionBySlug, firstAbility.
  • CombatService::runRound: Ally-Zug/Runde — Verbündeter greift mit an; Gegner zielt mit 35 % auf ihn (Ohnmacht statt Tod). Fähigkeiten: Begleiter auf Knopf (ally_ability-Flag), NPC autonom (KI, sobald Cooldown bereit). 4 Effekt-Typen strike/dot/buff (via Buff-System)/guard (Selbstheilung) + Cooldown.
  • combat.php: Ally-Bestimmung beim Start (?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.
  • Content (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).
  • End-to-end getestet (Wolf greift mit + wird getroffen + persistiert; NPC Hagen nutzt autonom „Gezielter Schuss"; Wolf L5 nutzt „Rudelheulen" auf Knopf → Spieler-Buff +3).
  • Offen: Quest-Navigate &ally=npc_hagen (Quest/Dialog-KI), Phase D Stall-UI, Begleiter-Portraits (Grafik-KI).

Feature: Companion-System Phase B (Fangen + Minispiel)

  • combat.php: Badge „⊂ Fangbar" (gold, pulsierend) bei 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.
  • fangen.php (NEU): 5-Runden-Timing-Minispiel. Server generiert die Zonen (Start-Breite aus 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).
  • fangen_ajax.php (NEU, op=resolve): Login + Token-Abgleich + aktiver Kampf gegen dieselbe fangbare Kreatur + Fanggeschirr-Besitz; verbraucht 1 Fanggeschirr; wertet die Stopp-Positionen serverseitig gegen die gespeicherten Zonen (kein Client-Win-Vertrauen). 0–2 Treffer = Fehlschlag (Tier entwischt, zurück in den Kampf), 3 = Fang (grade 0), 4 = grade 1, 5 = grade 2 → Companion::catch, halbe EP, kein Loot, Session beendet. Stall-voll-Abfang.
  • common.php: fangen.php + fangen_ajax.php in navExempt (Minispiel ohne Nav-Flow + AJAX).
  • combat.css: .tameable-Chip + .ca-tame-Button-Styling.
  • End-to-end getestet (Fanggeschirr → Kampf → 5/5 → Perfekter Fang → Item −1, Session weg, +EP).
  • Item-KI: Fanggeschirr-Definition geliefert (is-21). Offen: max_stack 1→höher (Nachtrag im Todo). Nächste: Phase C (Begleiter kämpft mit) + D (Stall & Training).

Feature: Companion-System Phase A (Datenfundament)

  • Tabellen (patch.sql): 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 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.
  • End-to-end getestet (fangen → Stats → aktiv → 50 Interaktionen Level-Up → Ohnmacht → Newday-Erholung).
  • Nächste Phasen: B Fangen-Minispiel (fangen.php) + Badge, C Begleiter kämpft mit, D Stall & Training.

Dungeon: Gegner-Level = Stufen-Nummer (statt Spielerlevel)

Feature: Special-Loot im Siegbildschirm hervorgehoben

  • 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.

Bugfix: Im Dungeon bei 0 Kampfrunden festgesteckt

Balance: Räuberlager-Stufen-Eskalation entschärft

Balance: Kampfbalance — „Hump"-Gear-Kurve + Kreatur-Normalisierung

  • 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.
  • Default bleibt absolute (gated), der Settings-Seed schaltet auf hump.
  • Params (balance_lab getunt): k=0.78, T=0.52, cushion=0.80, floor=0.52.

Balance: Dynamischer Kreatur-Multiplikator (global + Gear-basiert)

  • 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.

Feature: Kampf-Balancing-Simulator (CombatSim)

  • require/CombatSim.php — portiert rollDamage / resolveEnemyAttack / Initiative /
  • tools/combat_balance_sim.php — CLI-Report. Args: `--levels=1-15 --runs=100 --seed=42
  • admin_grotte.php — neuer Handler op=combat_balance (Level 4): HTML-Heatmap mit
  • common.phprequire/CombatSim.php registriert.

Balance: Kreaturen-Rebalance auf 1/1-Spieler-Ökonomie

  • 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).
  • att-Cut zuerst auf ×0.35 → voll Ausgerüstete gewannen ab L2 jeden Einzelkampf zu 100% (zu leicht). Auf ×0.5 angehoben → nackt Ø ~77%, halb Ø ~91% (L1) / ~99% (L2+), voll weiterhin ~100% ab L2.
  • Struktureller Befund: Full-Gear-Einzelkämpfe sind mit den verfügbaren Hebeln NICHT auf eine echte Win-Rate <100% bringbar. Monster-Angriff bewegt voll-Gear kaum (Def-Wall + 10·Level HP drücken Treffer auf min 1). 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.
  • Offen/optional: Trait-Konstanten (stacheln/panzerbrechend/berserkerrausch) + Dungeon-level_bonus noch im Old-Economy-Maßstab — nach Playtest per Sim nachziehbar.

Bugfix: Start-Werte Angriff/Verteidigung — 10 statt 1

  • seed_fix_startstats_att_def_20260530.php setzt startattack=1, startdefense=1 (idempotent).
  • Character::create() Code-Fallback startattack/startdefense von 10 → 1.
  • Es gibt keine intelligence-Spalte — „int" = initiative; applyLevelUpGains() erhöht korrekt initiative (+1/Level). Keine Änderung nötig.
  • Hinweis: bestehende Test-Charaktere behalten ihre alten Werte (nur Neuanlagen betroffen).

Bugfix: Fähigkeiten im Dungeon nicht nutzbar

Dungeon: Kampfrunden-Kosten — einmalig beim Eintritt statt pro Kampf

  • 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.
  • Meisterkampf/normale Kämpfe unverändert (dungeonRunId = 0 → ziehen weiter 1 Runde).

Dungeon-Intro: Beute-Vorschau „Chance auf" (kampfklassen-gefiltert)

  • 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.
  • Stufen-Anzeige vereinfacht: Intro-Badge + Scene-Label zeigen nur noch „Stufe N" (kein „/ M").
  • Loot-Fix (Seed): 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.php

Bugfix: Charakter-Slots in acclogin wechselten Reihenfolge nach Level

Feature: Dungeon-Stufen sind CLEAR-getrieben (nicht mehr Quest-getriggert)

  • Dungeon::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).
  • Schema: dungeon_definitions.final_repeatable (TINYINT, Default 0).
  • Zugang = Gate: Story-Dungeons haben keinen öffentlichen Ort → nur Quest-navigate → Story pacet die Stufen von selbst. Schwierigkeit (leicht/normal/schwer) bleibt pro Lauf wählbar.
  • Räuberlager: Trigger aus den Phasen entfernt, final_repeatable=0 (Story). Verlauf getestet: 0 Clears→Stufe 1, +1→Stufe 2, +2→Stufe 3, +3→gesperrt; mit final_repeatable=1→letzte farmbar.
  • Stufen-Anzeige: 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).

Feature: Dungeon-Phasen level_bonus + Räuberlager auf „Basis-Monster, nur stärker"

Bugfix: „Meisterkampfschleife" — stale Session kapert Folgekämpfe (combat.php)

Feature: Bach-Page um T-9-Kampf-Link erweitert (almhausen.php?op=bach)

Bugfix: Helga T-9 — Navigate-Choice zum Dungeon fehlte komplett (seed_fix_helga_t9_navigate_zum_dungeon_20260529.php)

  • „Ich möchte bleiben" → Quest-Annahme (cond quest_not_active → unsichtbar weil Quest schon active)
  • „Die drei Kämpfe…" → Quest-Abgabe (cond objectives_met → unsichtbar weil Boss noch nicht tot)
  • „Ich kämpfe noch — komme bald zurück" → nur Abbruch, kein Weiter
  • ✓ „Auf zum Mühlenbach!" (führt in den Dungeon)
  • ✓ „Ich kämpfe noch — komme bald zurück." (Abbruch)
  • ✓ Flavor-Choices

Feature: Räuberlager-Quests folgen Clear-Modell — alle Stufen → raeuberhauptmann (seed_fix_raeuber_quests_clear_modell_20260529.php)

  • ernst_sack/t7c_angebot: „Ihr Anführer ist nicht der gleiche — dunkler, gepanzert" raus → neutral
  • helga_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"

Bugfix: Hilde-Dialog 3× duplizierte „Die Riesenratte ist erledigt"-Choice (seed_fix_hilde_intro_dedup_20260529.php)

Feature: T-9 auf Räuberlager Phase 3 umgestellt (seed_fix_t9_auf_raeuberlager_phase3_20260529.php)

  • alm_t9_ernte Objective: monster:'*' × 3kill: raeuberkoenig × 1
  • Description: erzählt vom Räuberkönig + Mühlenbach
  • Helga t9_beweis: Lines auf Räuberlager-Kontext + navigate direkt zu combat.php?type=dungeon&op=intro&slug=raeuberlager_muehlenbach
  • Helga t9_nach_sieg: Lines „Der Räuberkönig ist gefallen…" (Tutorial-Closing-Beat erhalten)
  • Rewards bleiben (80 EP + Holzbalken/Eisennagel + Flags baurecht_bauerngasse + edahnien_freigeschaltet)
  • Phase v3 in dungeon_definitions.raeuberlager_muehlenbach.phases (Trigger quest:alm_t9_ernte,status:active, boss raeuberkoenig)
  • Kreaturen: strassenraeuber_meister, schattenschurke_meister, raeuberkoenig

Bugfix: Klara-Meisterkampf-Lock + Post-Quest-Wiedereröffnung (seed_fix_klara_meisterkampf_lock_20260529.php)

UX: Klara-Meisterkampf — 1-Klick direkt in den Kampf (seed_fix_klara_meisterkampf_direkt_20260529.php)

Balance: Schadensformel entzittert (CombatService::rollDamage)

Feature (WIP): „Über unser Dorf" — Dorf-Chronik Almhausen (almhausen.php)

  • Rathaus-Nav: neuer Link „Über unser Dorf" → almhausen.php?op=dorf_info
  • op=dorf_info: Coming-Soon-Stub (📜 + Platzhaltertext). Echte Pergament-Seite
  • Todo: todos/dialog_konrad_ueber_unser_dorf.md (NPC-Dialog-KI) — Choice bei Konrad

UX: Regen-Text klargestellt (tickt am Rundenbeginn) + roher Status-Log entfernt

  • applySkillEffects: 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.

Fähigkeiten-System — 3 neue Skill-Mechaniken + erste kunst-Skills (sk-08)

  • HoT / Regeneration: Spieler-Statuseffekt regen (Heilung pro Runde) — im Rundenbeginn-Tick verarbeitet. Status-Effekte tragen jetzt ein target ('self'|'enemy'); applySkillEffects routet entsprechend (self → addStatusEffect, enemy → addEnemyStatusEffect).
  • Blind: Gegner-Status 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).
  • kunst 8 Lichtmagie — „Heilendes Licht": regen +6 LP/Runde (2 Rd) + Gegner blenden (70% Fehltreffer, 2 Rd). 6 MP.
  • kunst 7 Schwarzmagie — „Lebensraub": ×2.2 Schlag + 70% Lebensraub. 7 MP.
  • Die übrigen 6 künste (Schwert/Axt/Lanze/Bogen/Armbrust/Elementar) folgen, sobald definiert.

Fähigkeiten-System — Skill-Tab im Kampf-Dock (sk-06)

  • 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.

Warum

Balance: Proc-Bonus-Wertespannen deutlich erhöht (require/Item.phpBONUS_RANGES)

Items: alm_t8b_jaeger Quest-Items + Fanggeschirr (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)

[0.2.4] – 2026-05-30

Bugfix: Falsches Kampf-Hintergrundbild in den Almhängen (combat.php)

Bugfix: Schwarzer Rahmen um Kreatur-Portraits (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.
  • Battle-Portrait: img bekommt Klasse ckill-black/ckill-white. combat.css: .ckill-blackmix-blend-mode:screen, .ckill-whitefilter:url(#crt-white-remove).
  • Intro + End/Sieg-Thumbnail: Inline-Compositing per killmode (schwarz→screen, weiß→white-remove).

Bugfix: Kampfanimationen wieder aktiv (combat.php)

  • Helfer 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.

Fähigkeiten-System — Combat-Integration + persistenter MP-Pool

  • 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.
  • Settings: startmp=10, levelup_mp=5.

Feature: 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_not
  • docs/dialog_referenz.md: give_skill in Effekt-Tabelle, has_skill/has_skill_not in Conditions-Tabelle.
  • CLAUDE.md: beide Tabellen ergänzt.

Fähigkeiten-System (Skills) — Fundament

  • Schema (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.
  • Settings (seed_settings_progression.php): startmp=10, levelup_mp=5.
  • devstatus: neues Modul 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 Setting
  • Setting startmp=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)

Meisterkampf = Level-Up auf JEDEM Level (Logikfehler behoben) + Phasen-Level-Cap

  • 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: Gegner = best-ausgerüstetes Spielerprofil + echter Level-Up-Stat-Gewinn

  • 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).

Meisterkampf: Voll-Heilung vor Kampfbeginn (combat.php)

Bugfix: Almhausen-Loot droppte zu oft (drop_chance Prozent statt Dezimal)

Change: Kampfkunst-Interstitial — kein Auto-Redirect mehr, nur Skip-Button (training.php)

  • setTimeout(go, 20000) — 20-s-Hardstop
  • setTimeout(…) 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)

Bugfix: Kampfkunst-Interstitial-Video-Pfad korrigiert (training.php)

  • training_schwert_2.mp4 gefunden (Perlös Waffe = Schwert)
  • training_axt_2.mp4 gefunden
  • training_lichtmagie_2.mp4 fehlt → fällt korrekt auf Schwert-Fallback zurück

Feature: Klara T-8 Flow konsolidiert — Annahme + Lehre + Animation in einem Klick (seed_fix_t8_flow_konsolidieren_20260529.php)

  • intro Choice → t8_ausbildung entfernt (war nach Konsolidierung nicht mehr erreichbar)
  • Nodes t8_ausbildung + t8_ausbildung_abschluss entfernt (toter Code)

Feature: Klaras Lehre schenkt Kampfkunst-Stufe 2 + spielt Interstitial (dialog.php, training.php, seed_fix_t8_ausbildung_kunststufe_20260529.php)

Feature: Meisterkampf als echter Kampf (require/Meisterkampf.php, combat.php, core/CombatService.php, require/CombatSession.php, training.php)

  • REQUIREMENTS (3 Stufen: Level/Kampfkunst-Stufe/freie EXP) — Single Source of Truth
  • nextStufe(), 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 Reward
  • Eintritts-Gate serverseitig (canChallenge) — direkter URL-Aufruf ohne erfüllte Voraussetzungen → Redirect zurück zum Ausbildungslager
  • Lehrmeister-Gegner via buildOpponent, CombatSession mit meisterkampf-Marker (in enemies[0])
  • Restriktionen: Item-Button + Fähigkeit-Button + Flucht-Button per JS ausgeblendet (CS.meisterkampf)
  • Reload-sicher (Resume-Branch erkennt den Marker)
  • isMeisterkampf() (Marker in enemies[0]). Bei Meisterkampf: Item-Procs aus ($procs = [])
  • Tod → applyDeath($char, isMk=true): immer geschützt, volle Heilung, kein Verlust, outcome meisterkampf_lost (kein Cooldown — sofort nochmal)
  • Sieg → Meisterkampf::applyVictory, kein normaler Loot, outcome meisterkampf_won

Feature: T-8 auf Level-Aufstieg umgebaut + Meisterkampf-Sieg erhöht jetzt Level (seed_fix_t8_meisterkampf_level_20260529.php, require/Quest.php, training.php)

Feature: Tutorial-Sterbeschutz (core/CombatService.php, combat.php)

  • Neuer Helper 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.
  • Alle drei Tod-Pfade (DoT-Tod, fehlgeschlagene Flucht, normaler Kampf-Tod) prüfen protected: bei Schutz outcome='lazarett' + freundliche Log-Zeile statt outcome='lose'.
  • Greift auch im Dungeon: der Run endet als died (fair, Fortschritt weg), aber der Char überlebt.

Feature: Kampfkunst-Freischalt-Interstitial mit Lehrer-Video (training.php, seed_kunst_unlock_content.php)

  • op=skill (Ausbildungsraum): Stufe-kaufen zeigt nicht mehr nur Inline-Text, sondern
  • NEU op=unlock: Vollbild-Overlay (z-index 99999, schwarz) mit mp4-Video des Lehrers +
  • seed_kunst_unlock_content.php: content_strings (training.php/de) — Titel, Weiter,
  • Mapping 7/8 geklärt + almhausen.php gefixt: kanonisch (Klasse.php/combat.php/tempel.php/
  • Todo: todos/grafik_video_kampfkunst_unlock.md — mp4-Clips (generisch + optional pro Stadt/Lehrer).
  • Gilt global (training.php, alle Städte). Meisterkampf-Sieg bekommt später eine eigene Animation.

Bugfix: Klara T-8 Folge-Nodes ohne navigate (seed_fix_klara_t8_folgenodes_20260529.php)

  • Beide Nodes bekommen 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).
  • [1] „Zeig mir was du kannst" → unsichtbar ✓ (flag_not: t8_kampfkunst_gelernt greift)
  • [2] „Ich bin bereit für den Meisterkampf" → SICHTBAR

Bugfix: Klara-Annahme-Loop + Hilde-T-7b + Helga-T-9 Cond schärfen (seed_fix_quest_annahme_loop_20260529.php)

  • [0] „Ich hab alle drei Aufträge erledigt" → unsichtbar
  • [1] „Zeig mir was du kannst" → SICHTBAR ✓ (nächster Schritt: Ausbildung)

Feature: Schwarzes Brett um T-7a/b/c-Quests erweitert + T-4-Slug-Bug (almhausen.php)

  • T-7a: „✓ Abgeschlossen"
  • T-7b: „⚔ Läuft"
  • T-7c: [Annehmen]-Button — kann jetzt entweder zu Ernst gehen ODER direkt übers Brett annehmen
  • T-4 + T-6: „✓ Abgeschlossen"

Systemfix: alle Quest-Effect-Nodes auf replay: "free" (seed_fix_quest_effect_nodes_replay_free_20260529.php)

  • Quest::accept() returnt true wenn schon active — keine Doppel-Annahme
  • Quest::complete() failt wenn Status != active — keine Doppel-Rewards
  • Mira rathaus/stempel_bogart, Mira anmeldung/bio_fehlt
  • Konrad rathaus_almhausen/stempel
  • Hilde schenke_almhausen/t2_abschluss, t4_abschluss, t7b_angebot ← gemeldeter Bug
  • Helga helga_t9/t9_beweis
  • Klara klara_t8/t8_quest_annehmen, t8_nach_sieg

Bugfix: Quest-Abgabe-Choices bleiben nach completed sichtbar (seed_fix_quest_abgabe_choices_20260529.php)

  • Cond bei allen drei Choices zusätzlich quest_active: <slug> — Choice jetzt nur sichtbar während Quest läuft
  • Hildes Choice-Text aktualisiert: „Kleeräupchen" → „Bergkraut" (Folge der Item-Umstellung aus qs-18)
  • Die drei Abgabe-Nodes (t7a_abgabe, t7b_abgabe, t9_nach_sieg) auf replay: "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).

Bugfix: Konrad-Dialog hatte noch zwei „Mondblumen"-Stellen nach Berg-Umstellung (seed_fix_konrad_t7a_restmondblumen_20260529.php)

Feature: Admin-Editor — Klasse/Kampfstil/Rasse mit Abhängigkeitsvalidierung

  • Neues Panel "Klasse, Kampfstil & Rasse" — alle drei Dropdowns in einer Grid-Zeile mit Setzen-Button
  • Live-JS-Validierung: cedCheckCompat() prüft beim Ändern jedes Dropdowns:
  • Server-seitige Validierung in op=stat_set: cedCheckCompat() nach jedem save() für race/kunst/specialty
  • cedCheckCompat(int $race, int $spec, int $kunst): ?string Funktion in admin_charedi.php

Cleanup: characters.klasse entfernt

  • patch.sql: ALTER TABLE characters DROP COLUMN IF EXISTS klasse — Spalte war Legacy, nie durch Wizard geschrieben, immer 0
  • require/Character.php: klasse() auf return 0 gestubbt mit @deprecated-Hinweis
  • dragon.php: toten if(klasse>0){nochange[klasse]=1} Block entfernt (war wirkungslos da klasse immer 0)

Was

  • 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 entfernt

Warum

  • Seeds mussten bisher nach jedem Patch manuell über das Dropdown einzeln eingespielt werden — fehleranfällig
  • boughttrain 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 redundant

Bugfix: Auto-Kampf-Buttons sahen wie Text aus — Fix im FALSCHEN CSS-File (templates/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")

P1-Bugfix: Loot-Items mit max_stack=1 verschluckten Drops (seed_fix_berg_loot_maxstack.php)

  • Berg-Loot gezielt: rohes_erz → 999, bergziegenfell/wildschwein_keule/bergkraut/baerenklaue → 99
  • Generelle Härtung: alle stackable=1 AND max_stack<=1 → 99 (fängt denselben Seed-Vergessen-Fehler bei anderen Items ab)
  • Lokal verifiziert: 0 verbleibende stackable Items mit max_stack<=1

Bugfix: Kampfkunst 7/8 vertauscht (Licht/Schwarz) — kosmetisch + Schaden-Engine (combat.php, require/Creature.php)

Bugfix: Kampfkunst 7/8 vertauscht im Admin-Editor (adminmodule/admin_charedi.php)

  • Drei Kampfkunst-Label-Maps korrigiert (Z.63 cedCheckCompat, Z.736 Stats-Box, Z.956 Validierungs-Panel): 7→Schwarzmagie, 8→Lichtmagie.
  • Erlaubnisliste $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.
  • Reine Code-/Kommentar-Korrektur, kein Schema-/Seed-Change. Labels und Erlaubnis-IDs stimmen jetzt durchgängig zur kanonischen ID-Bedeutung.

Bugfix: Phase-2-Boss (raeuberhauptmann_dunkler) hatte kein Kampf-Cinematic (combat.php)

Bugfix: Berg-Luchs droppte Bergziegenfell (seed_fix_berg_luchs_loot.php)

P0-Bugfix: HTTP-Test-Szenarien auf Live komplett rot — Runner-Self-Call lief über LAN-IP statt Loopback (require/BrowserSession.php)

Feature: Frisch-Installer / Installer-Paket (install.php, require/InstallerBuilder.php, admin_patch.php)

1. 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).
  • Wichtig: user (Singular!) — MasterBuilder::SKIP_DATA_TABLES listet versehentlich users (Plural), hier korrekt; sonst blieben die Alt-Accounts stehen.
  • Tabellen-Existenz wird geprüft (SHOW FULL TABLES) → nicht-existente Tabellen werden still übersprungen.
  • Kein Keep-Account, kein Reset-Modus mehr — der Admin wird im Wizard neu erstellt.

2. install.php (neu) — Standalone Setup-Wizard

  • Formular: DB-Zugang (Host/Port/User/Pass/Name) + Admin-Account (Login, Char-Name, E-Mail optional, Passwort ×2).
  • Bootstrap via dbwrapper.php + require/Settings.phpdb_pconnect() (Verbindungstest).
  • Schreibt dbconnect.php (var_export, 0640).
  • Führt install/schema.sqlinstall/gamedata.sqlinstall/admindata.sql (Wipe) der Reihe nach aus.
  • Legt danach den Admin an: User::create() + Character::create() + set('superuser',4)->set('tutorial_done',1)->set('city','edahnien')->save().
  • Eigener quote-/escape-/kommentar-bewusster SQL-Splitter (verifiziert gegen Bio mit \\", \'', \r\n).
  • Re-Run-Schutz: existiert dbconnect.php oder install/.lock → verlangt expliziten „überschreiben"-Haken. CSRF-Token. Passwort-Mindestlänge 6 + Bestätigung. Nach Erfolg: Selbstlösch-Button.

3. admin_patch.phpop=build_installer (neu)

  • Dialog: nur noch ☑ Code mitbündeln (kein Account-Picker mehr — Admin wird im Wizard erstellt).
  • Baut ZIP: manifest.json (type=installer) + install.php + install/{schema,gamedata,admindata}.sql + README_INSTALL.txt + (optional) kompletten Code.
  • schema.sql + gamedata.sql aus MasterBuilder::build(), admindata.sql (Wipe) aus InstallerBuilder::buildWipeSql().
  • Komplett-Code via 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).
  • Nav-Link „Installer-Paket erstellen" + allowPrefix('admin_patch.php?op=build_installer').

Verifiziert

  • buildWipeSql(): 14 Tabellen geleert (inkl. user Singular), 4 nicht-existente übersprungen, kein users (Plural) — 0 Fehler.
  • install.php-Bootstrap (read-only): getsetting() + User/Character::create + Settings::reload verfügbar, Race 1 vorhanden.
  • Admin-Anlage end-to-end (Wegwerf-Account): User+Char angelegt, SU 4 bestätigt, city/tutorial_done gesetzt, Cleanup ok.
  • SQL-Splitter: alle Statements DELETE/INSERT/SET, Bio-Stresstest = 1 Statement.
  • Lint grün: InstallerBuilder.php, install.php, admin_patch.php.

Hinweis Deploy

Feature: Tmp-Fehler aus _-Dateien direkt löschen (require/DbErrorLogger.php, require/PhpErrorLog.php, admin_grotte.php)

  • Liest alle JSON-Zeilen, entfernt jeden Eintrag dessen caller ODER ein trace-Frame aus einer _-Datei stammt (Dateiname = Teil vor erstem : im Frame-String).
  • Schreibt die Datei mit den verbleibenden Zeilen neu, gibt Anzahl entfernter Einträge zurück.
  • Entfernt zeilenweise alle Zeilen die eine _-Datei referenzieren (auch Stacktrace-Frames #0 /p/_x.php(5)).
  • Regex #(?:^|[\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.
  • Neuer Button „✂ Tmp-Fehler (_-Dateien)" neben „Log leeren" in beiden Viewern.
  • Eigene CSRF-Kontexte admin:dblog_tmp / admin:phplog_tmp, Action clear_tmp.
  • Regex-Matrix (8 Fälle): trifft _test_create.php, _introspect_tmp.php, _x.php(12), _diag.php; trifft NICHT village.php, _legacy\foo.php, dialog.php, logd_evo-Pfadnennung.
  • File-Rewriting end-to-end: DB-Log 3→1 (2 _-Einträge weg, village.php bleibt), PHP-Log 4→2 (2 _-Zeilen weg, village.php+dialog.php bleiben).
  • Lint grün: DbErrorLogger.php, PhpErrorLog.php, admin_grotte.php.

Bugfix: '[CHARACTER] Unbekanntes Feld übersprungen: stadt' Log-Spam (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).

[0.2.3b] – 2026-05-29

P0-Bugfix: Reload startet neuen Kampf mit neuer Kreatur (combat.php)

  • Kreatur aus Session rekonstruiert (Slug → Creature::get() für Display-Felder, Kampfwerte + laufende HP aus Session)
  • Dungeon-State ($__dungeonInfo, Phase, Progress) aus dungeon_run_id + Dungeon::activeRun() wiederhergestellt
  • Wald/Zone: location-Param aus GET übernommen (backUrl/continueUrl bleiben korrekt)
  • CombatSession::start() übersprungen — Session unangetastet
  • HP-Balken ($enHpCur neu) + AP ($__apCur neu) starten auf echtem laufendem Stand statt Maximum

Bugfix: HTTP-Test-Szenarien auf Live komplett rot — Runner-Self-Call lief über LAN-IP statt Loopback (require/BrowserSession.php)

  • scenarios_functional: 21 Tests → 0 grün, 21 ⚠ error
  • scenarios_security: 11 Tests → 2 grün (die beiden *negativen* Smoke-Tests die einen Login-Fehler ERWARTEN), 9 ⚠ error
  • Der Test-Runner ruft per Definition IMMER den lokalen Server auf (Self-Call) — Loopback ist semantisch korrekt.
  • Hält die strikte Loopback-only-Security von TestRunnerAuth unverändert intakt (kein Aufweichen des IP-Checks).
  • Funktioniert auf jedem Server ohne Per-Server-Config (Pfad kommt aus SCRIPT_NAME/logd bzw. /logd_evo).
  • http statt https: Loopback braucht kein TLS, vermeidet Zertifikats-/SNI-Fehler in curl bei Self-Signed-Certs.
  • Der explizite Override settings.test_runner_base_url wird weiterhin respektiert (für abweichende Ports/Setups).

Warum

[0.2.3] – 2026-05-29

Feature: T-7a/T-7b/T-9 Wald-Quests komplett auf Almhänge umgestellt (seed_fix_quests_wald_zu_berg_20260529.php)

  • Konrad t7a_angebot / t7a_abgabe: „Mondblumen" → „Bergziegenfelle" (Pergamente)
  • Hilde t7b_angebot / t7b_abgabe: „Kleeräupchen im Wald" → „Bergkraut auf den Hängen"
  • Helga t9_beweis: „Drei Kämpfe im Wald" → „Drei Kämpfe auf den Almhängen"
  • T-7a active mit altem Mondblume-Progress: Mondblumen werden nicht mehr fürs Objective gezählt (bleiben im Inventar, harmlos). Stattdessen Bergziegenfelle sammeln.
  • Perlö #11222 hatte vor dem Seed bergziegenfell ×1 + bergkraut ×1 → nach Definition-Update direkt 1/3 bzw. 1/2 ohne expliziten DB-Eingriff (Quest::checkObjectives evaluiert frisch).

Change: Edahnien-Wald-Link komplett aus Almhausen-Nav entfernt (almhausen.php)

  • $forestUnlocked/$forestNeeded + Wald-Link in „Vor dem Dorf" entfernt — dort steht nur noch „Die Almhänge"
  • Projektleiter baut T-7a um (weg von der Mondblume-aus-Edahnien-Wald-Abhängigkeit), daher keine Quest-Brücke mehr nötig
  • T-9 zählt ohnehin monster:'*' (jeder Kill) → Almhänge-Kämpfe reichen
  • forest.php city-aware Rück-Nav bleibt drin (harmlos, falls Almhausen-Spieler den Wald je anderweitig erreicht)

Feature: Almhänge — Kampf-Trigger in Nav verschoben + Heil-Ort „Die Almhütte" (almhausen.php, seed_almhuette_content.php)

  • Content-Panel-Button „Den Hang hinauf" entfernt → jetzt Nav-Link unter Heading „Die Almhänge" (nur mit Runden > 0, konsistent zur forest.php-Logik)
  • Nav-Link „Die Almhütte — Rast & Heilung" (immer erreichbar)
  • Erschöpft-Hinweis wandert ins Content-Panel (wenn keine Runden)
  • Steinunterstand am Hang mit dem alten Senn — heilt gegen Gold, analog Waldhütte
  • Kosten = fehlende LP × getsetting('almberg_heal_cost_per_hp', 3)
  • Atomarer Gold-Abzug (tryDeductGold) + CSRF (almhCsrf('almhuette_heilen'))
  • LP-Stand-Anzeige, „keine Wunden"/„zu arm"-Fälle abgefangen
  • Gate wie Almhänge (T-4); reiner Text-Ort (NPC-Dialog-Baum optional als späteres Dialog-KI-Todo)
  • Hintergrund almhausen_almhuette.webp → Fallback auf almhausen_almberg.*

Combat: Almberg location-Param + zone-aware Rück-Nav + Trait-Cleanup (combat.php, seed_almberg_kreaturen.php)

  • Neuer GET-Param 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.
  • Neu: 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,lauernausweichen,tier (echtes ausweichen + INI 10 bleibt)
  • kraehenschwarm: +fliegend,tier; wildschwein + hoehlenbaer: +tier (Zähmbarkeits-Voraussetzung Phase 3)
  • Stats/spawn_weights unangetastet.

Change: Schenke-Speisekarte von der Seite entfernt — Bestellen kommt in Hildes Dialog (almhausen.php?op=schenke)

  • Entfernt aus op=schenke: die 5 Speisekarte-Nav-Links (schenke_bestellen-Links),
  • Bleibt: Intro-Panel, „Mein Zimmer"-Nav, back_dorf, Hildes Dialog (DialogSystem::render)
  • Bleibt erhalten: Kauf-Handler op=schenke_bestellen (CSRF + atomarer Gold-Abzug +
  • Todo angelegt: todos/dialog_hilde_bestellen.md (NPC-Dialog-KI) — Bestell-Shop in
  • Hinweis: Nach T-2 gibt es bis dahin keinen UI-Weg mehr zum Essen-Bestellen (zeitkritisch)

Content: Almberg-Kreaturen + Berg-Loot — Wildzone befülllt (seed_almberg_kreaturen.php, seed_almberg_loot.php, combat.php)

  • rohes_erz — uncommon → Brokk (Schmiede, fehlende Erzquelle)
  • bergziegenfell — common → Marta
  • wildschwein_keule — common → Hilde
  • bergkraut — common → Hilde / Heilmittel
  • baerenklaue — 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 eingebaut

Feature: „Die Almhänge" — eigene Almhausen-Wild-/Kampfzone (almhausen.php?op=almberg)

  • op=almberg POI-Seite: Atmosphäre + Runden-Check + Kampf-Button → combat.php?location=almberg_almhausen
  • Gate: freigeschaltet sobald Spieler kämpfen kann (prefs-Flag einmalig_riesenratte_keller ODER T-4 completed ODER SU) — dauerhaft
  • Hintergrund almhausen_almberg.webp (file_exists-Guard, Bild folgt)
  • Dorfplatz-Nav: Sektion „Vor dem Dorf" bündelt Die Almhänge (ab T-4) + In den Wald (ab T-7a)
  • Kampf-Button nur mit Runden > 0, sonst „erschöpft"-Hinweis
  • 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)

Bugfix: Wald von Almhausen aus nicht erreichbar (almhausen.php, forest.php)

  • Neue Dorfplatz-Sektion „Vor dem Dorf" mit Link „In den Wald" → forest.php
  • Gate $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.php
  • Alle 4 „Zurück zum Dorf"-Links + 3 Router::allow() nutzen jetzt $backUrl/$backLabel
  • Hintergrund city-aware: {city}_wald.png mit Fallback auf edahnien_wald.png
  • Content-Keys: back_almhausen (neu), back_village (bestehend)

Feature: Schwarzes Brett blendet nicht-verfügbare Aushänge aus (almhausen.php?op=schwarzes_brett)

  • Entry wird NUR gezeigt wenn Quest-Status konkret ist: annehmbar (Voraussetzung erfüllt) / ⚔ Läuft / ✓ Abgeschlossen
  • Ausgeblendet (continue):
  • voraus_hint-Anzeige entfällt (gesperrte Einträge sind jetzt unsichtbar statt nur ausgegraut)
  • Leeres-Brett-Fallback: „Zur Zeit hängen keine offenen Aufträge aus." (Content-Key brett_leer)

Feature: T-8 verlangt alle drei T-7 + Konrad als zentraler Hinweisgeber (seed_fix_t8_prereq_konrad_hinweis.php, require/DialogSystem.php)

Bugfix: Mühlenbach-Kampf wiederholbar nach Dungeon-Clear (almhausen.php?op=bach)

Bugfix: Admin-URLs leaken in badnav-Alternativen (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")

Feature: DB-Error-Log mit vollem Stack-Trace

  • Neues Feld trace[] pro Log-Eintrag — bis zu 10 Stack-Frames im Format
  • Interne Frames (Database.php, DbErrorLogger.php) werden gefiltert
  • caller-Feld bleibt erhalten (= trace[0]) für Backward-Compat
  • Ältere Einträge ohne trace werden vom Reader still ignoriert
  • Stack-Trace ist standardmäßig zu (<details> collapsed) damit die Liste
  • Klick auf "Trace anzeigen (N Frames)" öffnet die Liste
  • Erster Frame (= Direkt-Caller) ist farblich hervorgehoben

Bugfix: combat_sessions-Schema in docs/combat_referenz.md aktualisiert

Bugfix: Ernst-Quest-Effect-Nodes auf replay: "free" (seed_fix_ernst_replay_free.php)

Feature: PHP-Error-Logging aggressiv + sofort sichtbar (lokal)

1. PHP-Error-Reporting verschärft (common.php)

2. 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 Zeitstempel
  • lineCount() / sizeBytes() — Stats für Banner
  • clear() — Log leeren via Admin
  • Effizienter rückwärts-Tail ohne komplette Datei zu laden

3. Admin-Grotte: op=phplog Viewer (admin_grotte.php)

  • Pfad + Zeilen-Anzahl + Größe
  • „Log leeren"-Button (CSRF-geschützt)
  • 150 letzte Einträge mit Tag-Färbung (Fatal rot, Warning orange, Notice grau)
  • Color-coded Tags links: 🟡 Dialog/Quest, 🔴 CHARACTER, 🔴 BADNAV, 🟠 Warning, ⚫ Notice

4. Recent-Errors-Banner auf der Admin-Grotte-Hauptseite

5. Hauptmenü-Link

Was du jetzt davon hast

  • Aggressives Error-Reporting lokal → Notices, Warnings, Deprecateds fallen sofort auf statt sich anzusammeln
  • Live-Server unverändert (display_errors=0, log_errors=1) → kein Info-Leak im Browser, Logs trotzdem persistent
  • Banner zeigt frische Fehler beim Admin-Grotte-Aufruf — kein Greppen mehr nötig
  • Color-coded Viewer[Dialog-Effect] und [Quest-Reward] aus unserem letzten Logging-Patch sind sofort als gelbe Tags erkennbar
  • Apache-Restart-resistent — eigenes Log-File neben dem Apache-Log

Konsequenz für andere KIs

Feature: Inventar-Popup — Item-Bilder in Kacheln + Paper-Doll-Slots (require/Item.php, templates/css/inventory.css, templates/edahnien.css)

Kachel-Bilder (.iv-D-card-icon--img)

Paper-Doll-Bilder (.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)
  • Glyph-Modus unverändert wenn kein Bild vorhanden
  • 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 wiederherstellen

Räuber-Sets (3 Sets, 7 Items)

Bugfix: Item-Seeds mit falschen Spaltennamen (buyable/buy_pricekaufbar/price_gold)

  • buyable → korrekt: kaufbar
  • buy_price → korrekt: price_gold
  • seed_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)
  • Items direkt via MySQL-CLI eingespielt (Seeds werden beim nächsten admin_patch.php-Lauf als bereits-applied getracked)

[0.2.2b] – 2026-05-28

Bugfix [P0]: SQLSTATE[HY000] 2014 „Cannot execute queries while other unbuffered queries are active" (require/Database.php)

Todo: seed_raeuberset.php — unbekannte Spalte buyable (Combat-/Item-KI)

Doku-Cleanup: deploy.bat / robocopy aus CLAUDE.md entfernt

  • „Wann ist ein Patch fertig (Release)?" — präziser Schritt-für-Schritt-Workflow mit den drei Op-Namen (Release erstellen, Paket herunterladen, Paket installieren)
  • „Deploy-Ablauf (manuell durch Projektleiter)" — entfernt deploy.bat-Aufruf, ersetzt durch admin_patch-Sequenz mit SeedRunner-Idempotenz-Hinweis
  • „dbconnect.php wird nie übertragen" — korrigiert: Ausschluss steht im ZIP-Build (buildPatchZip()), nicht in einem Shell-Script
  • „files.txt"-Tabellenzeile umformuliert: kein robocopy mehr
  • Neue Anti-Pattern-Marker: „NIEMALS install.php ODER deploy.bat erwähnen — beide existieren nicht"

[0.2.2] – 2026-05-28

Feature: Kampf-Bonus-Integration — Item-Procs wirken im Kampf (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.
  • Status liegt in 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
  • DoT-Ticks pro Runde: ☠ Gift an <Name>: -N LP / ⚔ Bluten an <Name>: -N LP
  • Werte aller equipped Items werden ADDIERT (z.B. 2 Items mit je 5% poison → 10% gesamt)
  • Cap 95% pro Stat (verhindert 100%-Procs)
  • Lifesteal-Heal cap auf char.maxhitpoints

Engine + Content: Pool-Filter nach Kampfkunst (require/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)
  • Phase 1: Schwert/Schild je 22%, Bogen/Armbrust je 20%, Köcher 25%
  • Phase 2: Schwert 40% / Schild 35%, Bogen/Armbrust je 35%, Köcher 45%
  • Phase 1: 0.44 / 0.45 Items
  • Phase 2: 0.75 / 0.80 Items

Content: Special-Loot-Pools für Räuberhauptmann + dunkler Hauptmann (seed_raeuber_special_loot_pools.php)

  • raeuber_schwert 18%, raeuber_schild 18%, raeuber_bogen 14%, raeuber_armbrust 14%, raeuber_koecher 22%
  • raeuber_schwert 35%, raeuber_schild 30%, raeuber_bogen 28%, raeuber_armbrust 28%, raeuber_koecher 40%

Feature: Special-Loot-Engine — Pool-basierte Drops mit Qualitäts-Würfel (require/Loot.php, core/CombatService.php, patch.sql)

  • creature_definitions.special_loot_pool JSON — Mob-spezifischer Pool zusätzlich zur Standard-Loot-Tabelle
  • dungeon_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-Skalierung
  • rollPool($pool) — würfelt einen Pool, returnt getroffene Slugs
  • rollCombinedPools(...$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)
  • Nach Standard-Loot (Dungeon::complete → loot_table) wird der Special-Pool gerollt
  • Kombiniert: Mob-Pool aus Creature::get($bossSlug)['special_loot_pool'] + Dungeon-Pool aus $dungeonDef['special_loot_pool']
  • Item-Level + Difficulty werden an Loot::rollAndGive durchgereicht
  • Drops landen in $allItems mit slug/name/qty/quality/item_level — Loot-Box zeigt sie
  • Bonus-Anwendung im Kampf — mod_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).

Balancing: Bauernrüstung — Waffe & Schild eigenständig (seed_fix_bauernset_waffe_schild.php)

  • bauer_waffe + bauer_schild aus dem Set entfernt (set_slug = NULL)
  • bauer_waffe bekommt eigenen Stat: ATK +1
  • bauer_schild bekommt eigenen Stat: DEF +1
  • item_sets.bauernruestung: required_pieces 8 → 6
  • Set-Bonus bleibt unverändert: ATK +2, DEF +3, INI +1

Content: Räuber-Sets — zwei neue Ausrüstungssets aus der Räuberhöhle (seed_raeuberset.php, require/Item.php)

  • raeuber_schwert — waffe, ATK +1, Bonus-Pool: poison/bleed/crit/first_strike/lifesteal
  • raeuber_schild — schild, DEF +1, Bonus-Pool: parry/bleed/crit
  • raeuber_bogen — waffe, ATK +1, Bonus-Pool: poison/crit/first_strike
  • raeuber_armbrust — waffe, ATK +1, Bonus-Pool: poison/crit/first_strike
  • raeuber_koecher — schild-Slot (kein neuer Slot), INI +1, Bonus-Pool: crit/first_strike/poison
  • raeuber_stab — waffe, ATK +1, Bonus-Pool: lifesteal/crit/poison
  • raeuber_fokus — schild-Slot, INI +1, Bonus-Pool: lifesteal/crit/first_strike
  • Neue Auflösungsreihenfolge: img-Feld → equip/sets/{set_slug}/{slug}.webpequip/weapons/{slug}.webpitems/{slug}.png
  • Bauernrüstung-Teile: templates/assets/equip/sets/bauernruestung/{slug}.webp
  • Räuber-Items: templates/assets/equip/weapons/raeuber_*.webp
  • raeuber_armbrust hat kein eigenes Bild → img-Fallback auf armbrust.webp via Seed
  • Zur BONUS_POOL-, BONUS_LABELS- und BONUS_RANGES-Konstante hinzugefügt
  • Icon: , 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)
  • Ermöglicht korrekte Anzeige des CHR-Set-Bonus im Inventar-Popup

Feature: Item-Qualitäts-Bonus-Effekte — Proc-Chancen pro Stufe (require/Item.php)

  • Item::giveWithQuality($ownerId, $slug, $quality, $forcedBonuses) — gibt Item mit Qualität + rollt Boni
  • Item::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)

Feature: Item-Qualitätsstufen — Instanz-Qualität mit Stat-Multiplier (require/Item.php)

  • QUALITY_LABELS — deutsche Präfixe (Fein / Makellos / Meisterhaft / Legendär)
  • QUALITY_MULTIPLIER — Stat-Faktor: ×1.15 / ×1.30 / ×1.50 / ×2.00
  • getRarity() — Seltenheit steigt mit Qualität (fine=+1, superior=+2, masterwork=+3 Stufen)
  • Kachel-Name zeigt farbigen Badge (iv-quality-badge) wenn quality ≠ normal
  • Stat-Chips auf Kacheln rechnen Multiplikator mit, Farbe wechselt zu jade (iv-stat-chip-boosted)
  • Detail-Panel zeigt Badge + Qualitätslinie + geboostete Stats (iv-stat-boosted)
  • JS ivDetailHtml() kennt QUAL_MULT-Map und wendet Multiplikator clientseitig an
  • CSS: .iv-quality-badge.iq-*, .iv-quality-line.iq-*, .iv-stat-chip-boosted, .iv-stat-boosted b

Content: Räuber-Veteranen für Dungeon-Phase v2 (seed_raeuber_veteranen.php)

  • Mob-Veteranen: Level +1, HP +40–50%, ATK +1–2, ein neuer Trait
  • Boss: Level +2 (Lvl 4), HP +60%, ATK +2, +1 DEF, +1 INI, neuer Trait rachsüchtig (= bei 0-Schaden-Treffer +2 ATK nächste Runde)
  • Veteranen behalten Basis-Schwächen ihrer Linie (Schurke: Lanze ×1.4, Lichtmagie ×0.6)
  • Dunkler Hauptmann: Magie ×1.4 (statt ×1.5 Basis — gewöhnter an Magier), Schwert/Bogen ×0.6 (statt 0.7 — schwarze Platte ist dichter)
  • Mondstahl 50% (qty 2-3)
  • Felsschuppe 35% (qty 2-3)
  • Drachenzahn 20% (qty 1-2)
  • Holzbalken 30% (qty 3-5)
  • Eisennagel 25% (qty 2-4)
  • Giftdrüse 18% (qty 1-2)

Feature: Dungeon-Phasen — gleicher Slug, alternative Mob-Konfiguration (require/Dungeon.php, combat.php, patch.sql)

  • dungeon_definitions.phases JSON — Array von Phase-Overrides
  • character_dungeon_runs.phase_id VARCHAR(40) — leerer String = Basis-Phase
  • character_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.
  • First-Clear-Milestone-Slug enthält Phase-Suffix wenn nicht Basis (z.B. 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.
  • Sonst: Intro mit Difficulty-Karten + Phase-Badge „⚡ Stufe X" oberhalb.
  • Resume-Banner + Scene-Label zeigen Phase-Label.

Feature: Mühlenbach Phase 2 — almhausen.php?op=bach erweitert

  • Erweiterter Gate: T-6 active/completed oder T-7c active/completed oder SU
  • Neues Branch-System (4 Zustände statt 2):
  • Dungeon-URL in beiden aktiven Phasen gleich: combat.php?type=dungeon&op=intro&slug=raeuberlager_muehlenbach
  • Dungeon::hasCleared()-Abhängigkeit entfernt — Quest-Status ist die Single Source of Truth

Feature: Schwarzes Brett — Quest-Annehmen-Buttons + Mühlenbach POI (almhausen.php)

  • Brett-Einträge mit Quest haben jetzt dynamische Status-Badges + Annehmen-Buttons
  • 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
  • Schmiede + Ziege bleiben reiner Flavor (kein Quest)
  • Badges: ⚔ Läuft (jade) / ✓ Abgeschlossen (dim)
  • Handler: ?annehmen=SLUG&_t=TOKEN — CSRF via almhCsrf('brett_annehmen'), Whitelist $brettAcceptable
  • T-3 selbst: kein Button (auto-complete on Visit — Ausnahme wie gewünscht)
  • Neue POI-Seite für T-6: Atmosphäre + 🗡 Kämpfencombat.php?type=quest&slug=waldraeuber
  • Gate: T-6 active/completed oder SU
  • Dorfplatz-Nav: „Am Mühlenbach" erscheint unter „Am Bach" sobald T-6 gestartet
  • Hintergrund: almhausen_bach.webp (file_exists-Guard)

Feature: alm_t7c_bach als Dungeon-Phase-2 — Räuber kehren zurück (seed_quest_alm_t7c_bach_phase2.php)

  • description: dunkler Anführer, lernfähige Räuber
  • objectives: kill: raeuberhauptmann_dunkler × 1 (analog Phase 1, Boss-Kill schließt ab)
  • rewards: 250 EP + 200 Gold (höher als T-6: 150 + 120)
  • prerequisite + giver_npc (alm_t6_muehle / ernst_sack) bleiben unverändert
  • 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-Annahme
  • Combat-Content-KI: strassenraeuber_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.
  • Almhausen-KI: op=bach Atmosphäre + Gate um V2-Trigger erweitern (siehe Test-Plan in todos/quest_raeuberlager_phase2_neuer_angriff.md Abschnitt 4–5).

Todo: Dialog-Engine — Cond-Refresh nach State-mutierenden Effects (todos/dialog_system_cond_refresh_after_effects.md)

Bugfix: Ernst-Choices nach Quest-Abschluss korrekt ausblenden (seed_fix_ernst_post_completion.php)

  • Cond verschärft (vier Stellen im 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.

Feature: Hilde-Zimmermiete-Schulden-Dialog + Flag-System-Konsolidierung (seed_fix_hilde_zimmer_schulden.php, dialog.php, require/DialogSystem.php, almhausen.php)

Bugfix: Ernst-Intro-Choices — quest_not_donequest_not_active (seed_fix_quest_alm_t6_intro_choice_cond.php)

  • Choice 0 „Marta schickt mich — wegen des Baches." (alm_t6_muehle)
  • Choice 2 „Gibt es neue Probleme am Bach?" (alm_t7c_bach) — gleiches Pattern, gleicher Fix

Was

Router: replaceUrl() + Quest-Nav nav_redirects

  • core/Router.php: neue Methode replaceUrl(string $urlPrefix, string $newUrl)
  • require/Quest.php: applyQuestNav() verarbeitet jetzt auch nav_redirects JSON-Feld
  • patches/current/patch.sql: ALTER TABLE quest_definitions ADD COLUMN nav_redirects JSON NULL.

Bugfix: Wizard nav-back (Feedback P2)

  • wizard.php op=ausweis: "← Zurück"-Link entfernt — verhindert vorzeitiges Verlassen

Wizard stuck-account Fallback

  • common.php: Fallback-Check nach Charakter-Load — wenn wizard_done-Flag fehlt
  • wizard.php op=finish: setzt wizard_done = true in prefs['flags'] bevor Redirect.
  • patches/current/seed_wizard_done_backfill.php: Backfill-Seed für bestehende Charaktere

item_badge_pending global in Output::render() zentralisiert

  • core/Output.php: konsumiert $_SESSION['session']['item_badge_pending'] vor dem
  • almhausen.php: lokalen item_badge_pending-Block in op=markt entfernt (jetzt zentral).

Bugfix: Tab-Reihenfolge auf der Anmeldeseite

  • templates/layout/landing.php: E-Mail-Input (#lf-email) erhält tabindex="-1"

Warum

  • Quest-Nav nav_redirects: ermöglicht deklarative URL-Umleitungen per Quest-State
  • Wizard nav-back: verhindert stuck accounts wenn Spieler den Dialog vorzeitig verlassen.
  • Wizard Fallback: fängt alle stuck accounts ab die es dennoch schaffen den Wizard zu
  • item_badge_pending zentralisiert: Badge-Animation läuft jetzt auf ALLEN Seiten
  • Tab-Reihenfolge: verhindert dass Spieler versehentlich ein E-Mail-Feld im

[0.2.1] – 2026-05-28

UX-Aufräumung: Räuberlager nur noch über den Bach erreichbar (almhausen.php, seed_fix_quest_alm_t6_remove_navextras.php)

  • Quest active → Atmosphären-Text + Gefahr-Text + Kämpfen-Button
  • Quest completed → „Am Bach ist es ruhig. Die Räuber sind fort…" (kein Button)
  • Quest none → kein Zugang zum Bach (Redirect zu almhausen.php)

Bugfix: Dungeon-Boss gibt keinen EXP/kein Gold (core/CombatService.php)

  • Leicht: Boss-EXP/Gold ×0.5
  • Normal: ×1.0
  • Schwer: ×1.5

UX-Fix: Battle-Nav-Buttons fühlten sich nicht klickbar an (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).

P0-Bugfix: Dungeon-Engine erkannte Win nicht als Dungeon-Sieg (core/CombatService.php, ajax/combat_ajax.php, common.php)

Asset: Boss-Cinematic für Räuberhauptmann (raeuberhauptmann.mp4)

Dungeon-BG: Räuberlager-Hintergrund auf almhausen_bach umgestellt (combat.php, seed_fix_dungeon_raeuberlager_bg.php)

UX-Fix: Dungeon-Intro — Resume-Banner bei aktivem Run (combat.php)

  • Bernsteine Hervorhebung mit „⚡ Aktiver Run"
  • Aktuelle Difficulty + Fortschritt (Kampf X von Y) + Progress-Bar
  • Zwei Buttons:

Balancing: Ranzige Bauernausrüstung — Set-Bonus erhöht (seed_fix_bauernset_bonus.php)

Feature: Inventar-Popup — CSS für Set-Bonus-Panel im Charakter-Bereich

[0.2.0] – 2026-05-28

Feature: Dungeon-System (Phase 1)

  • 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-Tracking
  • combat_sessions.dungeon_run_id — Verknüpfung CombatSession → Run
  • get/all/save/deactivate — Definitions-API (Admin)
  • canEnter — Tageslimit + Cooldown-Check (real-day-boundary)
  • startRun — Mob-Queue shuffled, Boss am Ende fix
  • activeRun / currentEnemySlug / isBossFight / progress / difficulty — UI-Helper
  • onFightWon — HP-Restore (flat/percent) + Buff::apply + index++
  • complete — Boss-Loot mit difficulty.loot_mult, Milestone bei First-Clear
  • flee / onDeath / abortMidFight — Run-Cleanup
  • CombatSession::start($char, $creature, $mode, $dungeonRunId=null) — neuer optionaler Parameter
  • CombatService::applyWin — verzweigt: Dungeon-Run? Mob-Belohnungen je nach monster_rewards_enabled, dann Dungeon::onFightWon → bei Boss-Sieg Dungeon::complete
  • CombatService::runRound — neues outcome 'dungeon_next' für Mid-Fight-Sieg (UI zeigt Zwischen-Screen statt Sieg-Screen)
  • Tod + Flucht im Dungeon: 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.
  • Scene-Label zeigt „Dungeon · Schwierigkeit · Kampf X / Y", Battle-Nav zeigt „Dungeon verlassen" statt direkter Flucht-Link
  • Neue Op 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-Button
  • In-Fight-Flucht bei Dungeon: confirm()-Dialog vorher („Run wird zurückgesetzt")
  • Nav-Button „Dungeon verlassen": ruft dungeonFleeConfirm() → POST dungeon_flee → Redirect
  • Dungeon-Boss-Sieg: bestehender Sieg-Screen, plus „★ Erster Sieg auf dieser Schwierigkeit"-Hinweis wenn first_clear
  • Wenn Dungeon::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.
  • Bei jedem Seitenaufruf (ausgenommen combat.php, newday.php, navExempt/AJAX): wenn CombatSession.dungeon_run_id aktiv → Dungeon::abortMidFight → Run als Flucht, Session weg, Toast-Flag in Session.
  • Inventar-Popups + AJAX-Endpoints triggern den Abort NICHT (Tränke nutzbar mid-fight, wie gewünscht).
  • „Räuberlager am Mühlenbach" (slug: raeuberlager_muehlenbach)
  • Sequenz: 2× strassenraeuber + 1× schattenschurke (Shuffle), Boss: raeuberhauptmann
  • HP-Restore: 8 LP flat zwischen Kämpfen
  • Mob-Loot deaktiviert, Boss-Loot via raeuberhauptmann_loot Tabelle
  • 3 Difficulties: Leicht (Lvl -1, Loot ×0.5), Normal (Lvl ±0, ×1.0), Schwer (Lvl +2, ×1.5)
  • Kein Tageslimit, kein Cooldown
  • Erreichbar: noch über keine UI verlinkt — testbar via combat.php?type=dungeon&op=intro&slug=raeuberlager_muehlenbach

Feature: Drei menschliche Räuber-Varianten (seed_raeuber_varianten.php)

  • Straßenräuber (Level 1) — aggressiv + goldräuber. HP 16 / ATK 3 / DEF 1 / INI 4 / SPD 5.
  • Schattenschurke (Level 1) — giftig + ausweichen + feigling. HP 10 / ATK 5 / DEF 0 /
  • Räuberhauptmann (Level 2, Elite, Bach-Boss) — gepanzert + panzerbrechend + berserkerrausch.

Bugfix: Kampf-Hintergrundbild ohne Dateiendung (404) (combat_forest_bg.php)

Feature: Mühlenbach als dedizierte Kampf-Location (almhausen.php?op=bach)

  • op=bach Block in almhausen.php — Gate: T-6 aktiv oder abgeschlossen (oder SU), sonst Redirect zum Dorfplatz
  • Atmosphären-Text am Bach (Schilf, Spuren, Waldräuber die auftauchen)
  • 🗡 Kämpfen-Button → combat.php?type=quest&slug=waldraeuber (selbes Pattern wie Keller-Ratte)
  • Nach Quest-Abschluss: ruhige „Bach ist frei"-Meldung statt Kampf-Button
  • Hintergrundbild: almhausen_bach.webp / .png — beide mit file_exists()-Guard (Bild wird separat eingespielt)
  • Dorfplatz-Nav: unter „Am Bach"-Heading erscheint „Am Mühlenbach" sobald T-6 active oder completed (oder SU)
  • $t6QStatus Variable ergänzt damit active/completed separat prüfbar bleiben

Feature: alm_t6_muehle auf Dungeon-System umgestellt (seed_fix_quest_alm_t6_ernst_sackgasse.php)

Fix: Inventar-Popup nach 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.php

Feature: Marta-T5-Reward auf komplettes Bauernset umstellen (seed_fix_quest_alm_t5_markt_bauernset.php)

  • quest_definitions.alm_t5_markt:
  • dialog_trees marta_brey/markt_almhausen Node t5_tausch_beides:

Bugfix: Log-Spam „[CHARACTER] Unbekanntes Feld" + Patchnotes-Popup persistiert nicht

  • Fix: ALTER TABLE characters ADD COLUMN IF NOT EXISTS patch_seen VARCHAR(20) NOT NULL DEFAULT '' AFTER laston in patch.sql. Lokal getestet — Spalte angelegt.
  • Fix: Neue 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.

Feature: Error-Logging für Dialog-Effekte + Quest-Rewards (dialog.php, require/Quest.php)

  • Apache-Error-Log: tail /var/log/apache2/error.log — mit Prefix [Dialog-Effect] / [Quest-Reward] / [Quest-Deliver] leicht zu greppen
  • DB-Tabelle debuglog (über Logger::system-Pfad) — schon vorhanden
  • Wenn ein Admin-UI für debuglog gewünscht ist → Boy-Scout-Todo

Feature: item_badge_pending in Output::render() zentralisiert

  • core/Output.php (render()): $_SESSION['session']['item_badge_pending']-Block
  • almhausen.php: Bestehenden item_badge_pending-Block (op=markt) entfernt —

Feature: Kein Reload nach Marta-T5-Tausch — In-Dialog Inventar-Feedback (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:

Feature: Ausruhen im Zimmer (almhausen.php, newday.php, core/charstats_func_inc.php)

  • Nav-Link in op=zimmer: "Ausruhen (LP auffüllen)" — nur sichtbar wenn HP < max UND Cooldown nicht aktiv
  • Handler: prüft Zimmer-Flag, Cooldown, HP-Stand → setHitpoints(maxHitpoints) → setzt alm_zimmer_ausgeruht_heute
  • Animation: vitalReward('hp', healed, {newPct: 100}) — HP-Balken füllt sich animiert, "+X LP" schwebt auf
  • Cooldown-Reset in newday.php — Flag wird gemeinsam mit inn_essen_heute gelöscht
  • vitalReward Typ 'hp' in charstats_func_inc.php ergänzt: Bar-Animation + roter Floating-Label

Feature: Inventar-Popup — Set-Bonus-Panel im Charakter-Bereich

Feature: Set-Bonus-System + Ranzige Bauernausrüstung (Starter-Set)

  • loadEquipmentBonuses(): 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ück
  • 1× Set-Definition bauernruestung — ATT +1, DEF +2, INI +1 bei allen 8 Teilen
  • 8× Equipment (Einzel-Stats alle 0): bauer_helm, bauer_brust, bauer_beine, bauer_schulter, bauer_arme, bauer_schuhe, bauer_waffe, bauer_schild

Bugfix: 9 weitere kaputte Seeds aus v0.1.9 ersetzt (Hilde T-4 + Marta T-5)

  • seed_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.

Feature: Release-Block bei pending Seeds (admin_patch.php op=release, ti-23)

  • Roter Banner mit Begründung warum geblockt
  • Button „▶ Erst Seeds einspielen" → öffnet die Seed-Übersicht
  • Override unter <details>: „⚠ Trotzdem releasen (Seeds als Snapshot überspringen)" — nur wenn man genau weiß warum

Feature: SeedRunner robust gegen exit() / 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")

Boy-Scout: RateLimit::cleanupExpired() in setnewday.php (Vorgänger-Handover-TODO)

Feature: DialogAjax + dialog.php auf Csrf-Klasse migriert (Token-Schema Phase 2)

Bugfix: JSON-Prefs-Fallback in common.php (ti-after-replay-fix)

Boy-Scout: Replacement-Bundle-Beobachtung

[0.1.9] – 2026-05-27

Docs: Dialog-System Vollreferenz (docs/dialog_referenz.md)

  • Abschnitt 4 + 12 + 19: terminal: true + navigate kombinierbar — autoTerminal() POSTet

Fix: quest_none-Condition implementiert (require/DialogSystem.php)

  • Abschnitt 4 + 12 + 19: terminal: true + navigate kann kombiniert werden —

Fix: quest_none-Condition fehlte (require/DialogSystem.php)

Feature: SU Dialog-Tree Inspector (require/DialogSystem.php + ui.css + dlg-system.js)

  • Header: npc_slug / context_slug + direkter Link zum Dialog-Editor
  • Tree-Ansicht — Nodes sind nach Abhängigkeit eingerückt (DFS vom start-Node, depth * 16px):
  • Je Node: Icon + #id + Speaker-Slug + Meta-Tags (start/terminal/navigate/on_show/anim/widget/action/input) + Zeilen-Preview (90Z) + Effect-Badges
  • Choices ohne next (action/input): werden als Mini-Liste im Node selbst angezeigt
  • Aktiver Node: grüne Border + Hintergrundtint, scrollt bei offenem Panel ins Bild
  • Raw JSON (aufklappbar <details>) — pretty-printed
  • DialogSystem::buildSuPanel() + DialogSystem::dfsBuild() — neue private static Methoden
  • dfsBuild(): rekursiv, Zyklus-Schutz via $visited-Array, Orphan-Detection anschließend
  • Kein Extra-Query — nutzt $rawNodes + $treeRow['tree_json'] aus bestehendem Build-Flow

UX: Marktwaren aus Marta-Seite entfernt (almhausen.php)

Fix: Hintergrundbilder Schenke + Rathaus (almhausen.php)

Feature + Bugfix: Schwarzes Brett Almhausen (almhausen.php + seed_fix_schwarzes_brett_navigate.php)

  • Setzt t3_brett_gelesen-Flag → löst T-3-Objective + Auto-Complete aus
  • 4 Aushänge als gestaltete Zettel (Keller-Riesenratte [rot/dringend], Mühle, Schmiede, Ziege)
  • Hintergrund: rathaus_schwarzes_brett.webp
  • konrad_fels/rathaus_almhausen → Node zu_schwarzes_brett: navigate gefixt
  • hilde_kranz/schwarzes_brett → Node brett_selbst: navigate gefixt

UX: Speisekarte-Links hinter T-2-Gate (almhausen.php)

Bugfix: Newday-Check nur auf Dorfplatz (almhausen.php)

Fix: Schenke-Name "Goldene Ähre" → "Goldener Pflug" (almhausen.php)

Fix: hilde_kamm — Portrait-Bild (seed_fix_hilde_avatar.php)

Fix: konrad_fels — Portrait-Bild (seed_fix_konrad_avatar.php)

Fix: Quest::adminReset() — Items + Reward-Flags löschen (require/Quest.php)

  • DB-Eintrag in character_quests löschen
  • Alle dlg_done:*-Flags aus prefs löschen (Dialog-Replay-Protection zurücksetzen)
  • Flag-type-Objectives aus prefs löschen
  • Reward-Flags aus prefs löschen
  • Reward-Items via Item::take() entfernen

Fix: Dorfplatz-Szene nicht getriggert nach Quest-Reset (almhausen.php)

Fix: T-2 "Die erste Nacht" — Auto-Complete + T-3-Trigger (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)
  • T-2-Abschluss-Choice aus intro entfernt (nicht mehr nötig)
  • T-3-Beschreibung neutral formuliert (kein Dialog impliziert)

Fix: village.php — Edahnien-Zugriffschutz (village.php, seed_fix_t9_edahnien_flag.php)

  • village.php: Guard leitet nicht-freigeschaltete Chars zu almhausen.php um (Admins ausgenommen)
  • Flag edahnien_freigeschaltet wird als Reward von alm_t9_ernte gesetzt (Seed eingespielt)

Fix: T-4 "Das Kellerproblem" — Abschluss-Dialog + Toggle + Live-Update

  • t4_abschluss.navigate = 'almhausen.php?op=schenke' → nach Quest-Complete sofort Redirect + Reload
  • t4_abschluss-Choice in intro: quest_active: alm_t4_keller ergänzt → Choice unsichtbar nach Quest-Complete

Fix: Marta T-5 Dialog steckt fest (seed_fix_marta_t5_tausch.php)

  • quest_active aus Choice 1 ([Fell und Zahn übergeben]) entfernt → nur has_item: rattenfell nötig
  • give_quest: alm_t5_markt als erster Effect in t5_tausch_beides ergänzt (vor complete_quest) → Quest-State wird sicher gesetzt unabhängig vom Einstiegsweg

[0.1.8] – 2026-05-27

Feature: Patch-Tool aufgeräumt — Snapshot in jedem Patch (admin_patch.php, CLAUDE.md)

Was sich ändert

  • Ruft beim ZIP-Build automatisch MasterBuilder::build('v' . $version) auf
  • Legt zusätzlich zu den bisherigen Files (patch.sql, patch.md, seeds, files.txt) auch schema.sql + gamedata.sql ins ZIP
  • manifest.json enthält jetzt snapshot-Sektion mit tables_dumped, tables_skipped, errors
  • Single-Patch-Pfad neu in 4 Stufen:
  • op=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)

Bilder-Drift-Warnung

CLAUDE.md

Was du jetzt davon hast

  • Jeder neue Patch-Upload bringt den Test-/Live-Server in den exakten Spieldaten-Zustand des lokalen Servers — kein Drift mehr
  • User-Daten (characters, items, houses, chat_messages, …) bleiben unangetastet (siehe MasterBuilder::SKIP_DATA_TABLES)
  • Sicherheits-Tabellen (rate_buckets, security_log, character_change_log) ebenfalls ausgeschlossen
  • Vergessene Bilder werden bereits beim lokalen Vorbereiten sichtbar (Banner)

Patch wird größer

[0.1.7c] – 2026-05-26

Feature: SeedRunner — zuverlässiges Seed-Einspielen mit Tracking (require/SeedRunner.php, admin_patch.php, patch.sql)

  • DB-Tabelle 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:
  • Hauptseite (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.
  • Sicherheits-/Test-KI kann via Read cache/seed-runs/latest.md direkt prüfen was beim letzten Lauf passiert ist
  • Beim Upload auf Live wird nicht mehr alles wieder eingespielt — nur die fehlenden
  • Failed Seeds bleiben sichtbar und können einzeln nochmal probiert werden

Feature + Bugfix: Schwarzes Brett in Almhausen (almhausen.php + seed_fix_schwarzes_brett_navigate.php)

  • Setzt t3_brett_gelesen-Flag → löst T-3-Objective aus + Auto-Complete der Quest
  • Zeigt 4 Aushänge (Keller-Riesenratte [DRINGEND], Mühle, Schmiede, Ziege) als
  • Nutzt Rathaus-Hintergrund, Titel "Schwarzes Brett"
  • konrad_fels/rathaus_almhausen → Node zu_schwarzes_brett
  • hilde_kranz/schwarzes_brett → Node brett_selbst

Fix: Almhausen Schenke-Name "Goldene Ähre" → "Goldener Pflug" (almhausen.php)

  • schenke_intro: 'Der Schankraum der "Goldenen Ähre"...'
  • flavor_abend: 'Aus der Goldenen Ähre dringt Licht...'

Bugfix: almhausen.php triggert Newday nicht (almhausen.php)

UX: Speisekarte-Links im Goldenen Pflug hinter T-2-Gate (almhausen.php)

Fix: konrad_fels/rathaus_almhausen — Anmeldung + Begriffe + Brief-Choice

Feedback-Fixes (Syméon, 2026-05-24)

Bugfix: Biographie-Text — Plural → Singular (require/Item.php)

Feature: Dialog-Scroll-Hinweis-Pfeil (templates/css/ui.css + dlg-system.js)

  • Bouncing -Pfeil via ::after auf .dlg-stage.scroll-hint-visible
  • Erscheint wenn User nach oben gescrollt hat (nicht am Ende des Stage)
  • Verschwindet nach scrollStage() (= neue Inhalte kommen, scrollen zu Ende)
  • Seite scrollt smooth zu den Choice-Buttons wenn diese unterhalb des Viewports liegen
  • Todo feedback_heldenausweis_scroll_hinweis.md erledigt.

Feature: Dialog-Text sans-serif (templates/css/ui.css)

Fix: Anführungszeichen-Muster ." " in Dialog-Bäumen (seed_fix_dialog_quotes.php)

  • konrad_fels/rathaus_almhausen — ueber_dorf, brief_hinweis, arbeit
  • marta_brey/markt_almhausen — t5_tausch_check
  • milla/dorfplatz_szene — garrik_szene
  • hilde_kranz/schwarzes_brett — brett_zeigen (zwei Zeilen zu einer zusammengeführt)

Fix: konrad_fels/rathaus_almhausen — Anmeldung + Begriffe + Brief-Choice + Navigate (seed_fix_konrad_anmeldung.php)

Fix: Garrik-Portrait in milla/dorfplatz_szene (seed_fix_garrik_avatar.php)

  • npc_descriptions Eintrag für garrik: avatar_path = characters/npcs/garrik_portrait.webp
  • garrik_einwand + garrik_geh speaker von 'player' auf 'garrik' geändert
  • tree['npcs']['garrik'] inline-Sektion mit color #8b7355 ergänzt

Refactor: Narrator-Speaker in milla/dorfplatz_szene (seed_fix_dorfplatz_narrator.php)

Fix: milla/zimmer_t2 Dialog-Tree fehlte in DB (seed_dialog_milla_zimmer_t2.php)

Fix: Neue Condition quest_not_active + Hilde bestellen während T-2 (require/DialogSystem.php + seed_fix_hilde_bestellen.php)

  • Gibt false zurück wenn Quest-Status === 'active' (Choice verstecken)
  • Erlaubt Choice wenn Quest nicht gestartet oder bereits abgeschlossen

Regel: Entwicklungsstand pflegen ist ab sofort PFLICHT-Check für alle KIs

  • CLAUDE.md — neue Goldene Regel 9 ("Entwicklungsstand pflegen — PFLICHT nach jedem Abschluss") plus ausführlicher Detail-Block in den Permanenten Arbeitsregeln. Die bisherige Regel 9 (Spezialisierte KIs / Todo-Delegation) rückt auf Position 10. Der Detail-Block beschreibt:
  • patches/devstatus.json — neues Modul 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.

[0.1.7a] – 2026-05-26

Was

Warum

[0.1.7] – 2026-05-26

Bugfix: almhausen.php triggert Newday nicht (almhausen.php)

P0-Bugfix: Ausweis-Animation wird nicht abgespielt (dlg-system.js)

P0-Bugfix: Counter-Bypass via allowPrefix (core/Router.php)

  • Counter-freie URLs (AJAX, JS-window.location, Hotspots): wie bisher prefix-fähig
  • URLs mit Counter aus addLink(): wie bisher exact-match (funktioniert)
  • URLs mit altem Counter aus Browser-History: abgelehnt → badnav
  • allowPrefix lehnt URL mit fremdem Counter ab (bfcache-Bypass-Schutz)
  • addLink generiert exakt-passenden Counter — kein Bypass via Prefix
  • allowPrefix Counter-Erkennung tolerant gegen Reihenfolge
  • admin_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).

P0-Bugfix: Browser-Back umgeht badnav via bfcache (common.php + JS-Guard)

  • docs/SICHERHEIT.md Regel 11 ergänzt um Browser-Back-Aspekt + Pflicht-Header
  • docs/SICHERHEIT.md Schwachstellen-Liste: neuer P0-Eintrag mit Exploit-Sequenzen + Fix-Beschreibung
  • docs/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)

Feature: Wizard stuck-account Fallback (wizard_done Flag)

  • wizard.php (op=finish): Setzt prefs['flags']['wizard_done'] = true bevor der
  • common.php: Neuer Guard nach Character-Load — prüft wizard_done-Flag (0 DB-Queries
  • patches/current/seed_wizard_done_backfill.php: Backfill für bestehende Chars

Fix: konrad_fels/rathaus_almhausen — Anmeldung + Begriffe + Brief-Choice

Fix: Almhausen Schenke-Name "Goldene Ähre" → "Goldener Pflug" (almhausen.php)

  • schenke_intro: 'Der Schankraum der "Goldenen Ähre"...'
  • flavor_abend: 'Aus der Goldenen Ähre dringt Licht...'

Feedback-Fixes (Syméon, 2026-05-24)

Bugfix: Biographie-Text — Plural → Singular (require/Item.php)

Feature: Dialog-Scroll-Hinweis-Pfeil (templates/css/ui.css + dlg-system.js)

  • Bouncing -Pfeil via ::after auf .dlg-stage.scroll-hint-visible
  • Erscheint wenn User nach oben gescrollt hat (nicht am Ende des Stage)
  • Verschwindet nach scrollStage() (= neue Inhalte kommen, scrollen zu Ende)
  • Seite scrollt smooth zu den Choice-Buttons wenn diese unterhalb des Viewports liegen
  • Todo feedback_heldenausweis_scroll_hinweis.md erledigt.

Feature: Dialog-Text sans-serif (templates/css/ui.css)

Bugfix: Milla/Garrik Szene — kein Redirect nach Terminal-Node

Feature: Erzähler-Sprecher im Dialog-System (speaker: "narrator")

  • Kein Avatar-Wrap, kein Sprecher-Label, kein Emotionsmarker
  • Transparenter Hintergrund, nur dezente linke Borderline (rgba(160,200,120,.20))
  • Schrift: 'IM Fell English', 'Georgia', serif + font-style: italic — literarische Prosa-Optik
  • Breite: 96% (volle Stage-Breite, zentriert)
  • Farbe: gedämpft (--ink-dim) — zurückhaltender als Sprecherblasen
  • resolveNpc(): gibt {name:'', avatar:'', color:'transparent', _isNarrator:true} zurück für narrator/erzaehler
  • show() (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-tag
  • appendDialogRow() (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
  • Keine ::before/::after-Pfeile auf Narrator-Bubble

Bugfix: Tutorial-Spieler landen in Edahnien (village.php)

  • Flag edahnien_freigeschaltet wird am Ende von T-9 ("Die Ernte vor dem Sturm")
  • Admins (isSuperuser ≥ 1) sind ausgenommen
  • Seed: patches/current/seed_fix_t9_edahnien_flag.php — ergänzt das Flag in T-9 rewards
  • cityUrl() in common.php gibt für almhausen bereits almhausen.php zurück —

Bugfix: T-2 "Die erste Nacht" — Quest auto-completed nicht, T-3 Beschreibung falsch

Bugfix: Dorfplatz-Szene startet nicht nach Quest-Reset (almhausen.php)

Bugfix: Quest-Editor — adminReset() setzt jetzt Dialog-Replay-Flags zurück (require/Quest.php)

Bugfix: Kampf-Hintergrundbild ohne Dateiendung (404) (combat_forest_bg.php)

Regel: Entwicklungsstand pflegen ist ab sofort PFLICHT-Check für alle KIs

  • CLAUDE.md — neue Goldene Regel 9 ("Entwicklungsstand pflegen — PFLICHT nach jedem Abschluss") plus ausführlicher Detail-Block in den Permanenten Arbeitsregeln. Die bisherige Regel 9 (Spezialisierte KIs / Todo-Delegation) rückt auf Position 10. Der Detail-Block beschreibt:
  • patches/devstatus.json — neues Modul 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.

[0.1.6d] – 2026-05-24

Was

Fix: konrad_fels/rathaus_almhausen — Stadtanmeldungs-Choice ergänzt

Warum

[0.1.6c] – 2026-05-24

Was

Fix: Dialog-Anführungszeichen-Pattern (." ")

  • konrad_fels/rathaus_almhausen: ueber_dorf, brief_hinweis, arbeit
  • marta_brey/markt_almhausen: t5_tausch_check
  • milla/dorfplatz_szene: garrik_szene
  • hilde_kranz/schwarzes_brett: brett_zeigen (Split-Quote bereinigt)

Warum

Bugfix: stadtanmeldung — Rathaus-Besuch wird nicht in Quest vermerkt

Bugfix: stadtanmeldung — Bio-Speicherung ohne Quest-Feedback

  • display_settings_ajax.php op=bio: prüft nach Save ob Bio neu befüllt wurde
  • templates/layout/info.php: showQuestBanner() aus IIFE heraus als
  • require/Item.php stSaveBio JS: bei res.quest_progress → ruft

[0.1.6b] – 2026-05-24

Bugfix [P1]: Test-Hub bricht mit HTTP 429 mittendrin ab — Reports werden nie geschrieben

Bugfix: Funktionale Inventory-Tests waren rot auf Test-Charakter mit echtem Inventar

  • require/Item.php — neue Methode Item::clearAll(int $ownerId): void
  • tests/functional/test_inventory.php — 5 Tests rufen Item::clearAll
  • tests/functional/test_dialog_effects.php — der „Kombination set_flag

[0.1.6] – 2026-05-24

Feature: Combat UI — Background per Slug, Nav-Lock, Animierter Intro, End-Screen-Fixes

  • combat.php: Hintergrundbild per Kreatur-Slug — dunklere Kulisse (wald_dunkel.png) für
  • combat.php: Navigation während Kampf gesperrt — Nicht-Admins sehen nur Auto-Buttons
  • combat.php: Animiertes WebP im Intro-Screen — {slug}-hq.webp wird als Animation
  • combat.php: COMBAT_STATE.creature.animation — neues Feld mit WebP-Pfad oder ''.
  • combat.php: COMBAT_STATE.type — neues Feld ('quest' oder ''), steuert End-Screen-Logik.
  • combat.php: End-Screen — Kreatur-Portrait/Animation wird jetzt auf Sieg- und Niederlage-
  • combat.php: End-Screen — "Weiter kämpfen"-Button nur bei Wald-Kämpfen mit Runden
  • combat.php: End-Screen — "← In den Wald" → "← Zurück" mit CS.backUrl (dynamisch je
  • templates/layout/partials/combat_forest_bg.php: Nutzt Output::getBackground() statt

Feature: Quest-Kreaturen T-4 + T-6/T-7c

  • seed_quest_creatures.php: riesenratte_keller (Level 1, regenerierend+einmalig) +
  • almhausen.php: op=keller — echten Kampfstart statt Stub, einmalig-Flag-Check
  • combat.php: type=quest + city-spezifische Spawn-Locations
  • ajax/combat_ajax.php: einmalig-Trait → prefs-Flag nach Kampfsieg

Bugfix: alm_t3_jobs — Schwarzes Brett ist im Rathaus, nicht in der Schenke

  • alm_t3_jobs Objective-Label korrigiert: "Taverne" → "Rathaus"
  • hilde_kamm/schenke_almhausen t3_brett-Node: schickt Spieler jetzt per navigate direkt
  • hilde_kranz/schwarzes_brett brett_selbst-Node: gebrochenes navigate
  • rathaus.php op=schwarzes_brett: T-3-Trigger (quest complete + accept + Aushang-Anzeige)

Bugfix [P0]: Quest-Fortschritt-Anzeige — Banner + Topbar-Blink

  • Quest-Banner blinkt jetzt bei allen 3 Typen: neue Quest / Abschluss / Alle Ziele erfüllt
  • Verschiedene Farben + Kicker-Texte je Typ (grün=completed, olive=progress, gold=new)
  • 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

Bugfix: rathaus.php — TypeError Quest::definition(NULL)

  • $q['quest_slug']$q['slug'] (Quest::active() liefert slug-Key, nicht quest_slug)
  • json_decode($q['progress'])$q['_progress'] (bereits decoded Array aus getWithDef())

Bugfix: dialog.php — Undefined variables in take_gold-Logger

  • $npc, $ctx, $nodeKey$npcSlug, $context, $nodeId in Logger::security()-Aufruf

Bugfix: alm_stadtanmeldung — Objective bio_filled wiederhergestellt

  • bio_filled war irrtümlich aus den stadtanmeldung-Objectives entfernt worden
  • Korrekte 3 Objectives wiederhergestellt: rathaus_anmeldung_besucht + bio_filled + ausweis_gestempelt
  • Dialog-Todo erstellt: konrad_fels muss anm_unterschreiben-Choice auf bio_filled gaten

Feature: Almhausen Zimmer — Truhe + sicherer Logout (op=zimmer)

  • Speicherort: characters.prefs['alm_zimmer_truhe'] = {gold: X, gems: Y} — kein Schema-Change
  • 4 neue POST-Ops: zimmer_truhe_ein / zimmer_truhe_ab / zimmer_gems_ein / zimmer_gems_ab
  • CSRF: almhCsrf('zimmer_truhe') im URL-Parameter _t
  • Gold/Gems-Deposit: tryDeductGold() / tryDeductGems() — atomar (SICHERHEIT.md Regel 9)
  • Withdraw: prefs-Grenzwert-Prüfung vor save()
  • Flash-Messages via $_SESSION['session']['almh_msg']
  • Guard: alle 4 Ops prüfen t2_zimmer_gemietet-Flag
  • almhGetTruhe(array $prefs): array — liest Truhen-Inhalt
  • almhZimmerMsg(string $type, string $text): never — Flash + redirect (DRY)

Bugfix [P0]: Almhausen — Newbie-Navigation ungegated

  • Markt: nach T-4 (einmalig_riesenratte_keller Flag / Quest alm_t4_kellerproblem)
  • Mühle: nach T-5 (Quest alm_t5_markt)
  • Schmiede + Hintere Gasse: nach T-6 (Quest alm_t6_muehle)
  • Ausbildungslager: nach T-7a ✓ (unverändert)
  • Bauerngasse: nach T-8 ✓ (unverändert)
  • Kutsche: nach T-9 / Item::has('eigentumsurkunde') — war komplett offen = Tutorial-Skip

Bugfix: Almhausen — Seitentitel "Burgplatz" blieb aus Session kleben

Feature: Admin-Editor — User-Ansicht + Superuser-Level

  • Suche gibt jetzt sowohl Charaktere als auch User-Accounts (login/email) aus — getrennte Sektionen in der Trefferliste
  • op=viewuser&uid=X — neue Ansicht: Account-Karte (login, email, Regdatum, laston, Test-Badge) + alle zugehörigen Charaktere als Tabelle mit Status-Spalten
  • Superuser-Level (0–4) per Dropdown + POST op=set_superuser setzbar — sowohl auf der User-Ansicht als auch im Char-Detail (op=view)
  • Jede Charakter-Zeile in der Suchliste hat 🧑-Icon → Link zum zugehörigen Account
  • require/CharacterLog.php neu: Whitelist-basiertes Feldänderungs-Log (32 Felder), detectSource() via Backtrace, in Character::save() eingehängt

Feature: Auslogort-Bonus — Schenke & Heim

  • Flag prefs['flags']['inn_zimmer_sicher'] wird bei Zimmerbuchung gesetzt
  • newday.php §5b: Buff ausgeruht_schenke (def+1, expires_type: newday) + extra Runden
  • houses.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)

Redesign: T-2 Zimmer-Flow — Milla begleitet Spieler aufs Zimmer

  • NPC milla / Kontext zimmer_t2
  • intro: 3 Choices — essen / truhe / schlafen
  • t2_essen: take_gold 5 + set_flag t2_gegessen (terminal → autoTerminal)
  • t2_truhe → auto-advance → t2_truhe_notiz
  • t2_truhe_notiz: set_flag t2_truhe_entdeckt + t2_truhe_freigeschaltet (terminal)
  • t2_schlafen: navigate → almhausen.php?op=schenke
  • DialogSystem::render('milla', 'zimmer_t2', $character) wenn Quest aktiv
  • t2_essen-Choice aus Hildes intro entfernt (Essen jetzt Zimmer/Milla)

[0.1.5] – 2026-05-24 · Almhausen

Feature: Almhausen Zimmermiete — "Zum goldenen Pflug"

  • dialog.php: 3 neue Effekttypen: alm_zimmer_mieten (Gold + prefs setzen),
  • require/DialogSystem.php: Neue Condition gold_max (Gegenstück zu gold_min)
  • require/Quest.php: Quest::assignOverdueRent() — Batch-Methode für setnewday.
  • setnewday.php: _nd_zimmermiete()Quest::assignOverdueRent().
  • patches/current/seed_zimmermiete.php: Quest + Hilde-Dialog (4 Choices, 7 Nodes).

Feature: Vitalinfo-Reward-Animationen (window.vitalReward)

  • core/charstats_func_inc.php: Inline-Script mit window.vitalReward am Ende von
  • templates/css/layout.css: @keyframes reward-gold-glow (Glow-Flash auf .gold-val),
  • templates/partials/dlg-system.js updateCharSidebar():

Dialog-Fix: Milla Dorfplatz-Szene (auto-advance)

  • Alle reinen Erzähl-/NPC-zu-NPC-Nodes nutzen jetzt next statt choices:[{text:"[zuhören]"}]
  • Garrik als zweiter NPC-Speaker im npcs-Block (speaker:'garrik' statt speaker:'player')
  • Einzige echte Spieler-Choices bleiben in milla_anerkennung
  • Nebenbei: Wirtin-Name korrigiert von "Hilde Kamm" auf "Hilde Kranz" (aktueller Slug)

Test-Hub: ein Editor für alle vier Test-Quellen (op=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:
  • Menü-Link in der Hauptseite zeigt nur noch „Tests (alle Quellen)" — die

Feature: Phase 3 — Race-Condition-Szenarien (HTTP-parallel)

  • require/BrowserSession.php: Neue Methode parallelRequest($requests) via
  • tests/scenarios/functional/scenario_race_atomic_gold.php (2 Szenarien):
  • tests/scenarios/functional/scenario_race_rate_limit.php (1 Szenario):

Bugfix [P2]: feedback.php — schwacher from-Filter (Path-Traversal-Restrisiko)

  • '../../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)
  • feedback.php: Neue Helper-Funktion sanitizeFeedbackFrom() extrahiert das erste
  • tests/security/test_feedback.php: Nutzt jetzt die echte Helper-Funktion via

Bugfix [P2]: BrowserSession — Counter-Regex schluckte den Bindestrich

  • require/BrowserSession.php: Regex auf [a-zA-Z0-9-]+ erweitert.
  • require/BrowserSession.php: Neue Helper assertTrue(), assertFalse(), assertNotOnBadnav()
  • admin_grotte.php: op=functests_run schreibt jetzt auch persistente Markdown-Reports

Feature: Phase 2 — Funktionale Tests (In-Process) + HTTP-Szenarien

  • SecurityTester.php: runAll() um optionalen $dir-Parameter erweitert — kann jetzt
  • admin_grotte.php: Neuer Handler op=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 auf
  • tests/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,

Bugfix [P1]: stadtanmeldung Quest kompletiert nicht (bio_filled-Blocker)

  • patches/current/seed_fix_quest_stadtanmeldung_objectives.php: Stellt korrekten
  • DialogSystem.php Zeilen 613–625: bio_filled: true + bio_empty: true als
  • todos/dialog_konrad_stadtanmeldung_bio_gate.md: Todo für Dialog-KI, damit der

Bugfix: dialog.php — take_gold-Case verwendete undefinierte Variablen

Almanac: combat_ajax.php — Quest::progressKill() nach Kampfsieg

Bugfix: training.php — t8_meisterkampf_gewonnen Flag nicht gesetzt

Almhausen — Tutorial-Integration (Milla/Dorfplatz + Lager + Bauerngasse)

  • almhausen.php: Milla-Dorfplatz-Szene (nach stadtanmeldung completed, vor dorfplatz_szene_gesehen),

Bugfix [P1]: Feedback als Popup statt Seitennavigation

  • feedback.php: AJAX-Modus ergänzt — bei _ajax=1 im POST wird JSON
  • templates/layout/partials/impressum_link.php: Feedback-Link öffnet jetzt
  • templates/edahnien.css: Neue Modal-Klassen: dialog.fb-modal,

Todo [P2]: Konrad Rathaus — Ausweis ohne Quest zugänglich

  • Condition quest_not_done → quest_active auf anm_start-Choice im
  • todos/almhausen_konrad_ausweis_quest_condition.md: vollständiger Seed-Code für Dialog-KI.

Todo [P3]: Newbie mit Quest hat vollen Gebäude-Zugriff

  • todos/almhausen_tutorial_gebaeude_gating.md: Implementierungsansatz für Almhausen-KI,

Bugfix [P0]: Almhausen — Newbie-Navigation ungegated (Permission Bypass)

Bugfix + Feature: Hilde Schenke — Zimmer mieten komplett (T-2)

  • t2_zimmer: terminal+effectsnavigate: almhausen.php?op=zimmer_mieten
  • Dialogtext korrigiert: "Schloss klemmt — Schlüssel zweimal drehen."
  • Neuer Intro-Choice "Aufs Zimmer." (condition: flag: t2_zimmer_gemietet) → t2_aufs_zimmer
  • Neuer Node t2_aufs_zimmer: [nickt kurz zur Treppe] → navigate: almhausen.php?op=zimmer
  • op=zimmer_mieten: idempotent (Flag-Guard), tryDeductGold(10) atomar,
  • op=zimmer: Guard (Flag muss gesetzt sein), Zimmer-Flavor-Text,

Bugfix: [P2] Wizard — "← Zurück" Nav-Link während Dialog ausgeblendet

  • wizard.php (op=ausweis): Router::addLink('← Zurück', $abandonUrl) entfernt.
  • todos/feedback_wizard_nav_back.md: Feedback als erledigt markiert.

Bugfix: Bürgerausweis → Heldenausweis (sichtbare Namen in DB)

  • seed_fix_heldenausweis.php: Umbenennung in 4 DB-Stellen —

Szenario-Runner: AJAX-Step-by-Step UI

  • admin_grotte.php: 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).
  • admin_grotte.php: Neuer Handler op=scenarios_step?ajax=1 mit drei Aktionen:
  • require/ScenarioRunner.php: Zwei neue Methoden für Step-API

Test-Result-Persistierung als Markdown

  • require/TestResultWriter.php: Neue Klasse 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.
  • common.php: require_once 'require/TestResultWriter.php' ergänzt
  • admin_grotte.php:

Phase 1b — Sicherheits-Szenarien (2026-05-24, Sicherheits-KI)

  • scenario_csrf_cross_action.php — 2 Szenarien: equip_ajax und house_ajax lehnen Token aus anderem Context / mit Garbage-Wert ab. Direkter Test der zentralen Csrf::verify-Pipeline über echte HTTP-Sessions.
  • scenario_rate_limit_burst.php — feedback:post unter Burst-Last (3 ok → 4. blockiert). Beweist dass rate_buckets-UPSERT auch über separate Worker-Prozesse race-safe inkrementiert. Inkl. Cleanup der Test-Reports aus dem feedback/-Ordner.
  • scenario_feedback_protection.php — 3 Szenarien: (a) POST ohne CSRF wird abgelehnt + keine Datei geschrieben, (b) 20000-Byte-Body wird auf <5000 Bytes gecappt, (c) <script>-Body wird 1:1 gespeichert (Beweis dass htmlspecialchars_decode wirklich raus ist) UND beim Render via htmlspecialchars korrekt escaped.
  • scenario_idor_equip.php — secsim_a bekommt ein equippable Item, secsim_b versucht es per equip_ajax auszurüsten. Erwartung: "nicht gefunden oder nicht deins", Item-Owner unverändert, kein equipped_slot gesetzt. Direkter Test von Regel 8 (Ownership-Check).
  • scenario_badnav_no_bypass.php — 2 Szenarien: (a) bei gesetzter restorepage erscheint KEIN prominenter "Zurück zum Dorf"-Bypass-Link und KEIN SU-Override für normale Spieler, (b) Loop-Detection-Hinweis "führt im Kreis" erscheint wenn badnav_loop_marker jünger als 10 Sek ist + restore-Link wird unterdrückt.
  • Dialog-Replay-Szenario — braucht aktiven Dialog-Tree mit Effect-Node. Wird in Phase 2 sinnvoll wenn Test-KI Quest-Szenarien baut die ohnehin ähnliches Setup haben.
  • Race-Condition (paralleles curl) — BrowserSession ist synchron. Erweiterung via curl_multi möglich, aber Aufwand vs. Mehrwert: die atomare Logik ist bereits durch Unit-Tests + Code-Review bewiesen.

Phase 1a — Test-Infrastruktur

Was

  • require/CookieJar.php — Cookie-Persistierung für HTTP-Sessions
  • require/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-Methoden
  • tests/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-Spalte
  • require/User.phpisTestAccount()-Getter
  • login.php — Guard: Test-Accounts können nur mit gültigem Runner-Nonce eingeloggt werden
  • admin_grotte.php — neuer Handler op=scenarios_run (Level 4, Rate-Limit, generiert/clearet Nonce)
  • common.php — Bootstrap der neuen Klassen
  • docs/test_architektur.md — Status auf "Phase 1a fertig" + Nonce-Mechanismus dokumentieren
  • docs/SICHERHEIT.md — neuer Abschnitt "Test-Infrastruktur" mit Mitigationen

Warum

Sicherheits-Mitigationen (Phase 1a)

Bugfix: Seed-Dateien — veraltete Item-Spaltennamen

  • patches/current/seed_items_tutorial.php: purchasablekaufbar, priceprice_gold
  • patches/current/seed_fix_hilde_zimmer.php: purchasablekaufbar, buy_priceprice_gold

Feature: CharacterLog — Automatisches Änderungs-Logging

  • Whitelist von 32 geloggten Feldern (LOGGED_FIELDS): gold, gems, goldinbank, mondsilber,
  • detectSource() via debug_backtrace() — erkennt automatisch aufrufende PHP-Datei + Zeile
  • setContext() / clearContext() — manueller Override für beschreibendere Quellen
  • record() — 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 ein
  • save() schreibt nach erfolgreichem UPDATE alle geloggten dirty Fields via CharacterLog::record()
  • tryDeductGold() + tryDeductGems(): expliziter CharacterLog::record()-Aufruf nach atomarem UPDATE

Feature: DB-Pflichtfelder — fehlende Spalten ergänzt

  • spirits TINYINT(2) DEFAULT 0
  • lastday DATETIME DEFAULT '2000-01-01 00:00:00'
  • killedin VARCHAR(100) DEFAULT ''
  • forest_fights SMALLINT UNSIGNED DEFAULT 0

Feature: Admin Charakter-Editor — User/Account-Integration

  • Suchbegriff trifft auf characters.name UND user.login / user.emailaddress
  • Account-Treffer werden als goldene Karten über der Charakter-Liste angezeigt
  • Char-Zeilen haben ein 🧑-Icon das zum zugehörigen Account springt
  • Zwei neue Direktsprung-Formulare: "Char per ID" + "Account per ID"
  • Zeigt User-Info (Login, E-Mail, Regdatum, letzte Aktivität, TEST-Badge)
  • Listet alle Charaktere des Accounts mit Level, Gold, Status
  • Je Charakter: Superuser-Level per Dropdown direkt setzbar
  • Je Charakter: "Bearbeiten →" Link zum Char-Editor
  • CSRF-gesichert via cedToken(charId)
  • Validiert Level im Bereich 0–4
  • Redirect zurück zu op=viewuser&uid=X (oder op=view&id=Y als Fallback)
  • Auch auf Char-Detailseite (op=view) als "Berechtigungen"-Panel verfügbar

[0.1.3] – 2026-05-24

Sicherheits-Fix: Feedback-Formular gehärtet (P0)

  • feedback.php: Vier Lücken im öffentlich erreichbaren Feedback-Endpunkt geschlossen
  • admin_grotte.php: fbSaveRatings() schreibt ratings.json jetzt mit LOCK_EX — keine Korruption bei parallelen rating/delete-Klicks
  • tests/security/test_feedback.php: 5 neue Tests — Konstanten-Bounds, CSRF-Token-Format für feedback:post-Context, RateLimit-Integration über shared context, mb_substr UTF-8-Sicherheit, from-Filter weist Path-Traversal/XSS-Versuche ab
  • docs/SICHERHEIT.md: Neuer P0-Eintrag mit Restrisiken (F-5 Datei-Overwrite-Race cosmetic, F-8 nginx-Migration als TODO)
  • docs/sicherheit_tests.md: Manueller Test-Block (7 Verifikations-Schritte inkl. CSRF-Stub-Test mit DevTools, 5 Regressions-Checks)

Feature: Quest-Kampf T-4 Riesenratte (Schenkenkeller Almhausen)

  • seed_quest_creatures.php: Neue Quest-Kreaturen geseedet:
  • almhausen.php: op=keller Stub durch echten Kampf-Start ersetzt
  • combat.php: type=quest und stadtspezifische Spawn-Location:
  • ajax/combat_ajax.php: Nach Kampfsieg Einmalig-Flag setzen

Feature: Patch-Manager — Release-Formular mit Name + Beschreibung

  • admin_patch.php: Release-Formular (op=release) um optionale Felder erweitert

Feature: Feedback-Viewer im Admin-Panel

  • admin_grotte.php: Neuer op=feedback-Handler (ab Level 1) — zeigt alle .md-Dateien aus feedback/ in der Admin-Grotte

Bugfix: riesenratte_keller — respawn_type ungültig

  • patch.sql: respawn_type war leer (Seed hatte 'once' verwendet, das nicht im ENUM ist).

Bugfix: training.php — t8_meisterkampf_gewonnen Flag fehlte

  • training.php: Nach erfolgreichem Meisterkampf bei city=almhausen wird jetzt

Was

  • almhausen.php: Navigation auf Dorfplatz überarbeitet — Kategorien nach
  • docs/almhausen_stadtplan.md: Ruf-System, Milla, Rettungslager, Schmiede-Korrektur,
  • docs/almhausen_tutorial.md: Vollständige Tutorial-Questreihe (T-0 bis T-9)

Warum

  • Navigation war inkonsistent (ein-Kategorie + NPC-Namen in Klammern)
  • Tutorial-Questreihe fehlte als eigenständiges Planungsdokument
  • Stadtplan brauchte Milla/Rettungslager und die neuen Konzepte (Ruf, Tutorial, Chats)

Neu: docs/combat_referenz.md

  • Vollständige Combat-Referenz für KI-Agents erstellt
  • Konsolidiert: combat_system.md + combat_plan.md + creature_system.md + Live-Code
  • Enthält: Flow, Klassen-API, AJAX-Sicherheit, DB-Schema, JS-API, Traits, Loot, Phase-2-Checkliste

Neu: docs/test_architektur.md (Konzept-Abstimmung)

  • Architektur für automatisierte Spiel- und Sicherheits-Tests dokumentiert
  • 80/20-Split: In-Process (SecurityTester) für Bausteine + HTTP-Szenarien für End-to-End-Vektoren
  • Gemeinsame Infrastruktur (BrowserSession + drei Test-Charaktere) für beide Welten
  • Phasen-Aufteilung: Phase 1 (Sicherheits-KI) baut Infrastruktur + Sicherheits-Szenarien,
  • Enthält Sicherheits-Implikationen + Mitigationen die in Phase 1 umgesetzt sein müssen
  • Keine Code-Änderung — reine Konzept-Doku zur Abstimmung mit der Sicherheits-KI

[0.1.2] – 2026-05-24

Bugfix: seed_items_tutorial.php — veraltete Spaltennamen

  • patches/current/seed_items_tutorial.php: purchasablekaufbar, priceprice_gold (Spaltennamen aus dem Legacy-Schema ersetzt)

Feature: CharacterLog — automatisches Änderungs-Protokoll

  • require/CharacterLog.php: Neue Klasse — protokolliert Feldänderungen in character_change_log.
  • require/Character.php:
  • patch.sql: character_change_log-Tabelle (id, character_id, field, old_value, new_value, delta, source, created_at)

DB-Bugfix: fehlende Spalten in characters

  • patch.sql: 4 fehlende Spalten nachgetragen — spirits, lastday, killedin, forest_fights.

DB-Cleanup: Legacy-Tabellen entfernt

  • setnewday.php: DELETE FROM commentary-Zeile ausgebaut
  • DROP TABLE: commentary, creatures, taunts gelöscht (alle MyISAM/latin1, Special-only, nicht mehr genutzt)

Feature: Bürgerausweis / Stadtanmeldung

  • almhausen.php: 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-Overlay
  • seed_dialog_mira_rathaus_anmeldung_link.php: Anmeldungs-Flow vollständig in konrad_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_erledigt
  • templates/partials/dlg-system.js: submitEffects() liest _node.navigate als Redirect-Ziel nach Ausweis-Animation (statt hardcodiertem rathaus.php)
  • rathaus.php: op=anmeldung — fälschlich eingefügte Heldenausweis-Logik entfernt (Almhausen-Anmeldung läuft jetzt vollständig über almhausen.php)
  • require/DialogSystem.php: Neue Conditions bio_filled + bio_empty in choiceAllowed()
  • dialog.php: Neuer Effect-Typ milestone_award — vergibt Milestone direkt im Dialog
  • templates/partials/dlg-system.js: play_ausweis_anim-Flag in submitEffects() — SVG-Unterschrift + Stempel-Animation + Milestone-Overlay
  • templates/css/layout.css: .ausweis-card + .ausweis-signing + .ausweis-stamped Styles + Animationen
  • seed_dialog_mira_anmeldung.php: Dialog-Tree konrad_fels/anmeldung mit bio_filled/bio_empty/quest_* Conditions (3 Objectives)
  • seed_quest_stadtanmeldung.php: Quest stadtanmeldung mit 3 Objectives (rathaus_besucht / bio_filled / ausweis_gestempelt) + Milestone in Rewards; giver: konrad_fels / almhausen_rathaus
  • require/Quest.php: Neuer Objective-Typ bio_filled in objectiveMet() + Popup-Tab-Switch
  • seed_dialog_mira_rathaus_anmeldung_link.php: Erweitert konrad_fels/rathaus_almhausen-Tree um Stadtanmeldungs-Choices

Item: Bürgerausweis

  • seed_item_buergerausweis.php: Neues Item buergerausweis — Kategorie urkunde, nicht stapelbar, nicht handelbar, keep_on_dragonkill=1 (bleibt nach Dragon-Kill)
  • seed_quest_stadtanmeldung.php: Item buergerausweis zu Quest-Rewards hinzugefügt; Objective-Labels "Mira" → "Konrad Fels" korrigiert

Quest-Konsolidierung: start_rathaus → stadtanmeldung

  • wizard.php: Starter-Quest bei Charakter-Erstellung auf stadtanmeldung umgestellt (war start_rathaus)
  • seed_quest_stadtanmeldung.php: Gold-Reward 0 → 50 (übernommen aus alter Quest); Beschreibung korrigiert ("Mira" → "Konrad Fels", Milla-Backstory ergänzt)
  • patch.sql: start_rathaus aus quest_definitions + character_quests löschen

Sicherheits-Regelwerk

  • docs/SICHERHEIT.md: Neues verbindliches Regelwerk für alle Code-Änderungen — Bedrohungsmodell, zehn Sicherheits-Gebote (Server vertraut keinem Client-Wert, AJAX-Drei-Stufen-Pflicht, Idempotenz, Dialog-Replay-Schutz, Whitelist statt navExempt, SU-Levels 0–4, SQL in Klassen, Ownership-Checks, atomare Updates, Output-Escaping) + Quick-Check-Liste + Liste bekannter Schwachstellen mit Priorität (P0: dialog.php-Effect-Replay, eval() in Router::loadFromDb; P1: Race-Condition im Shop, uneinheitliches Token-Schema; P2: Multiacc-Legacy, unserialize-Pfad, fehlendes Rate-Limit; P3: MD5-Passwörter)
  • CLAUDE.md: Verweis auf docs/SICHERHEIT.md im "Goldene Regeln"-Block ergänzt; bei Konflikt gewinnt SICHERHEIT.md
  • docs/sicherheit_tests.md: Neue Test-Checkliste die von der Sicherheits-KI nach jedem eingespielten Fix befüllt wird; aktuell mit dem ersten Eintrag für den atomaren Gold-Fix
  • admin_grotte.php: Neuer op=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) anzeigt
  • require/SecurityTester.php: Mini-Test-Framework — test() 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 geladen
  • tests/security/test_atomic_gold.php: 6 Tests für die neuen tryDeductGold/tryDeductGems-Methoden (positive/negative/zero/exakte-Balance/dirty-write-Pfad)
  • tests/security/test_dialog_ajax_validators.php: 9 Tests für die requireAmount-Validierungs-Logik (Regex, $min/$max, $max=-1 vs. $max=0 Edge-Case, SQL-Injection-Versuch)
  • tests/security/test_router_whitelist.php: 8 Tests für Router::allow/allowPrefix/clear/isAllowed/isPublicPage/isNavExempt + Sanity-Check dass keine Spielseite fälschlich in navExempt landet

Security-Log: Bug-Fix + Admin-UI

  • require/Logger.php: Logger::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 = '')
  • dialog.php: Beim erkannten Replay-Versuch wird jetzt Logger::security('dialog_replay_attempt', json{npc, ctx, node, mode, key}) aufgerufen — Admin sieht in der Übersicht ob/wie oft Replay-Versuche kommen
  • admin_grotte.php: Neuer op=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

Schutz vor Tabellen-Aufräumung durch DB-KI

  • require/RateLimit.php: Self-Disable bei DB-Fehler — neue static $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-Cache
  • require/MasterBuilder.php: rate_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)
  • todos/todo_db_maintenance.md: Neue Sektion "⚠ Sicherheits-Tabellen — NICHT droppen" mit Tabelle (rate_buckets, character_change_log, security_log), Zweck, Schreib-/Leseseite und Verfahrens-Anweisung für die DB-Pflege-KI

Sicherheits-Fix P0 (badnav-Dorf-Bypass entfernt + Loop-Fix mit Alternativen)

  • badnav.php: Komplett überarbeitet

Sicherheits-Fix P2 (unserialize-Härtung gegen Object-Injection)

  • require/Util.php: Neue Helper-Funktion 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
  • common.php: 7 Inline-Stellen umgestellt (Bootstrap dragonpoints/prefs/fertigkeiten/ausruestung/allowednavs/bufflist + dump_item)
  • acclogin.php: 5 Inline-Stellen umgestellt (Login + Prefs-Edit-Flow)
  • require/Character.php: 9 Inline-Stellen umgestellt (zentrale Getters für prefs/bufflist/dragonpoints/fertigkeiten/ausruestung + appearance, kleidung, bonus); benutzen den expliziten 2. unserialize-Parameter da Character.php auch von Code aufgerufen wird der Util noch nicht kennt
  • require/User.php: 1 Inline-Stelle (stats)
  • require/Mail.php: 1 Inline-Stelle (prefs für E-Mail-Farbcode-Entfernung)
  • adminmodule/admin_charedi.php: 1 Inline-Stelle (dragonpoints im Char-Editor)
  • tests/security/test_safe_unserialize.php: 6 Tests — Array/Skalar deserialisieren, leerer/null/garbage liefert Default, Objekt wird zu __PHP_Incomplete_Class, verschachteltes Objekt im Array auch geschützt, b:0; Edge-Case dokumentiert
  • docs/SICHERHEIT.md: P2 als Phase 1 erledigt markiert; Phase 2 (special/* Module, langfristige JSON-Migration für prefs) als Boy-Scout-TODO

Sicherheits-Fix P2 (Rate-Limit gegen Bot-Spam)

  • require/RateLimit.php: Neue Klasse mit DB-Storage (rate_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"
  • patches/current/patch.sql: Tabelle rate_buckets (PK character_id + context, count + reset_at als unix-Timestamp, Index auf reset_at für Cleanup)
  • common.php: RateLimit in require_once-Block
  • ajax/shop_ajax.php: RateLimit::check($charId, 'shop:buy', 30) als erstes im buy-Op — schützt vor Bot-Käufen (max 30/min = 1 alle 2 Sekunden)
  • inn.php: RateLimit::check($charId, 'inn:ale', 12) im ale-AJAX — verhindert Buff-Spam (12/min = 1 alle 5 Sekunden)
  • tests/security/test_rate_limit.php: 7 Tests — under/over limit, Context-Trennung, ungültige Eingaben, reset, status, require-Wurfverhalten
  • docs/SICHERHEIT.md: P2 als Phase 1 erledigt markiert; Phase 2 (weitere Endpunkte beim Anfassen, Cleanup-Cron in setnewday) als Boy-Scout-TODO

Feature: Patch-Manager — Ketten-ZIP (Chain-Update)

  • admin_patch.php: Neues op=pack_chain — bündelt alle Versionen seit dem letzten Master-Paket in einem einzigen ZIP

Sicherheits-Fix P2 (anticheat.php / ac_check entfernt)

  • anticheat.php: Datei komplett gelöscht. War totaler Code — ac_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 mehr
  • common.php: require_once "anticheat.php" entfernt, mit Kommentar dokumentiert warum + Hinweis auf characters.lastip/uniqueid falls Multiacc-Erkennung jemals neu gewünscht
  • docs/SICHERHEIT.md: P2-Eintrag als behoben markiert

Sicherheits-Fix P1 (CSRF-Token-Klasse vereinheitlicht)

  • require/Csrf.php: Neue zentrale Klasse — tokenFor(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"
  • common.php: Csrf in den require_once-Block aufgenommen
  • admin_grotte.php: 3 Inline-Token-Stellen ersetzt (seclog_clear, dblog_clear, quest_trigger pro Slug); ungenutzte $pageToken-Variable entfernt
  • require/Character.php: tryDeductGoldInBank loggt jetzt korrekt via CharacterLog (war Lücke — Gold und Gems loggten bereits)
  • ajax/equip_ajax.php + require/Item.php: equip-Token-Pipeline gleichzeitig auf Csrf::tokenFor('equip:set') / Csrf::verify() umgestellt; equip_ajax akzeptiert sowohl _token (Legacy) als auch _csrf (neu)
  • ajax/house_ajax.php + houses.php: house_ajax-Token-Pipeline gleichzeitig auf Csrf::tokenFor('house:ajax') umgestellt (houseToken()-Helper bleibt, delegiert intern)
  • tests/security/test_csrf.php: 8 Tests — Determinismus, Context-Unterscheidung, _POST/_GET-Reihenfolge, expliziter Token-Override, Format-Validierung (16 hex chars), requireValid-Wurfverhalten, hiddenInput-Escaping

Sicherheits-Fix P0 (eval() in Router::loadFromDb entfernt)

  • core/Router.php: 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).

Sicherheits-Fix P1 (Race-Conditions Phase 2 — 9 weitere Stellen)

  • require/Character.php: Neue Methode tryDeductGoldInBank(int): bool — analog zu tryDeductGold für das Bank-Konto, conditional UPDATE auf goldinbank >= ?
  • require/Item.php: Item::shopBuy() nutzt tryDeductGold() statt Read-then-Write
  • inn.php: 5 Stellen umgestellt — Ale (op=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 auftritt
  • fluchkessel.php: 4 Stellen — Heilen, Fluch entfernen, Gegengift, Trank brauen — DialogAjax::requireCondition durch tryDeductGold + DialogAjax::fail ersetzt
  • rathaus.php: 3 Stellen — Schwarzes-Brett-Gesuch (Gold-Check nach Limit-Check verschoben), Titel kaufen, Reparatur spenden
  • forest.php: Marta-Heilung — Gold-Check + Abzug atomar
  • burgviertel.php: 4 Stellen — Grundstück kaufen mit Refund-Pfad bei House::create-Fehler, Haus bauen, Haus upgraden, Raum bauen
  • bank.php: 4 Stellen — Einzahlen + Abheben (neue tryDeductGoldInBank), Schließfach mieten, Schließfach upgraden
  • almhausen.php: 2 Stellen — Gastwirt-Essen, Kutschen-Reise
  • tests/security/test_atomic_gold.php: Suite um Test für tryDeductGoldInBank erweitert

Sicherheits-Fix P0 (Dialog-Effekt-Replay)

  • dialog.php: Vor dem Effects-Loop neuer Replay-Guard — prüft Flag dlg_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"
  • require/Character.php: Drei Helper-Methoden dialogReplayKey($npc, $ctx, $node, $mode) / dialogReplayBlocked($key) / dialogReplayMark($key) — testbar isoliert vom dialog.php-Handler
  • tests/security/test_dialog_replay.php: 9 Tests (Key-Format, daily-Suffix, free-Modus liefert leeren Key, blocked vor/nach mark, DB-Persistenz, mehrere Marks koexistieren)
  • docs/SICHERHEIT.md: P0-Eintrag als behoben markiert; "replay"-JSON-Optionen dokumentiert
  • docs/sicherheit_tests.md: Test-Block ergänzt (8 manuelle Verifikationen, 7 Regressions-Checks)
  • core/Router.php: Neue Methoden Router::snapshot() / restoreSnapshot() — vom SecurityTester pro Test verwendet, damit Tests wie test_router_whitelist.php den Nav-State der laufenden Seite nicht verseuchen
  • require/SecurityTester.php: Vor und nach jedem Test wird Router::snapshot()/restoreSnapshot() aufgerufen — Tests dürfen Router::clear() etc. nutzen ohne die Ergebnis-Seite zu zerstören
  • admin_grotte.php: allowPrefix('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)

Sicherheits-Fix P1 (Race-Condition Gold/Gems)

  • require/Character.php: Neue atomare Methoden 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/Negative
  • ajax/shop_ajax.php: Buy-Op nutzt tryDeductGold() statt read-modify-write — schließt Dupe-Lücke bei parallelen Käufen
  • ajax/house_ajax.php: Einzahlen + gems_einzahlen nutzen tryDeductGold/Gems() analog
  • docs/SICHERHEIT.md: P1-Eintrag als behoben markiert; Liste der noch ungeprüften Stellen (Inn, Fluchkessel, Burgschmiede, Bank, Training) als TODO ergänzt

Feature: Quest-gesteuerte Nav-Umleitungen (nav_redirects)

  • core/Router.php: Neue Methode Router::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.
  • require/Quest.php: applyQuestNav() verarbeitet jetzt auch nav_redirects (drittes JSON-Feld neben nav_restrictions/nav_extras); SQL-Query + WHERE-Klausel entsprechend erweitert; Docblock aktualisiert.
  • patch.sql: ALTER TABLE quest_definitions ADD COLUMN IF NOT EXISTS nav_redirects JSON NULL AFTER nav_extras

Dokumentation

  • docs/quest_creation_guide.md: Vollständiges Quest-Erstellungs-Handbuch — DB-Schema (live), alle Objective-/Reward-Typen, NPC-Dialog-Integration (Conditions + Effekte), Router-Sicherheitsregeln, kill-Tracking, tägliche/wiederholbare Quests, Ketten-Quests, vollständiges Beispiel + Checkliste

Bugfix: Portrait-Auswahl zeigt nur Basis-Portraits

  • wizard.php + require/Character.php: portraitPath() 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 Index

Feature: Responsives Bild-System

  • core/Output.php: setBackground() normalisiert jetzt auf Basis-Pfad (Extension/Suffix-Strip); neue Methoden getBackgroundCss() (CSS image-set + Viewport-Breakpoints) + picture() (responsives <picture>-Element)
  • templates/layout/info.php: Background-Style durch Output::getBackgroundCss('.info-page::before', ...) ersetzt
  • templates/layout/scene.php: Background-Style durch Output::getBackgroundCss('.scene-bg', ...) ersetzt

NPCs + Dialoge: Almhausen Tutorial-Kette (T-2 bis Tutorial-Ende)

  • docs/npc_almhausen_tutorial.md: Vollständige NPC-Profile für alle 7 neuen Tutorial-NPCs (Garrik, Hilde Kranz, Marta Brey, Ernst Sack, Klara Frost, Helga Brandt, Lena Almhausen) — Aussehen, Persönlichkeit, Sprachstil, Wissen, Reaktionen, Grenzen, Geheimnisse, ai_prompts
  • docs/npc_registry.md: Neue NPCs ergänzt; Archetypen-Tabelle aktualisiert; slug-Namespace dokumentiert (lena_almhausen ≠ lena ≠ lena_weg)
  • patches/current/seed_npc_garrik.php: npc_descriptions für garrik (Szenen-Bauer, Dorfplatz — Chat-only, kein Dialog-Baum)
  • patches/current/seed_npc_hilde_kranz.php: npc_descriptions für hilde_kranz (Wirtin, almhausen_taverne)
  • patches/current/seed_npc_marta_brey.php: npc_descriptions UPDATE marta_brey (ai_prompt überarbeitet für Tutorial-Integration T-5/T-7)
  • patches/current/seed_npc_ernst_sack.php: npc_descriptions UPDATE ernst_sack (ai_prompt überarbeitet für Tutorial-Integration T-6/T-7; GROSSBUCHSTABEN-Pattern dokumentiert)
  • patches/current/seed_npc_klara_frost.php: npc_descriptions für klara_frost (Kampflehrerin, almhausen_ausbildungslager)
  • patches/current/seed_npc_helga_brandt.php: npc_descriptions für helga_brandt (Grundbesitzerin, almhausen_bauerngasse)
  • patches/current/seed_npc_lena_almhausen.php: npc_descriptions für lena_almhausen (Slug lena_almhausen — abgegrenzt von lena/lena_weg; Tutorial-Ende-NPC)
  • patches/current/seed_dialog_hilde_t2.php: Dialog-Tree hilde_kranz/erste_nacht (T-2 — Zimmer mieten, Essen; navigate zu almhausen.php op=zimmer_mieten/op=essen_kaufen)
  • patches/current/seed_dialog_hilde_t3.php: Dialog-Tree hilde_kranz/schwarzes_brett (T-3 — Brett zeigen, Keller-Quest annehmen; set_flag hilde_brett_gezeigt + hilde_keller_auftrag)
  • patches/current/seed_dialog_hilde_t4.php: Dialog-Trees hilde_kranz/keller_vorberei + hilde_kranz/keller_sieg (T-4 — Keller öffnen → navigate; nach Sieg: give_exp + give_item rattenfell + giftzahn + Marta-Hinweis)
  • patches/current/seed_dialog_marta_t5.php: Dialog-Trees marta_brey/markt + marta_brey/markt_nach_tausch (T-5 — Loot-Tausch navigate; Ernst-Hinweis nach Tausch; set_flag ernst_t6_freigegeben)
  • patches/current/seed_dialog_ernst_t6.php: Dialog-Trees ernst_sack/muehle + ernst_sack/muehle_sieg (T-6 — Waldräuber-Quest navigate; nach Sieg: give_gold 30 + give_exp 80 + set_flag klara_t8_freigegeben + ernst_t7_verfuegbar)
  • patches/current/seed_dialog_ernst_t7.php: Dialog-Tree ernst_sack/muehle_folge (T-7 — tägliche Folge-Aufgaben; condition flag/flag_not ernst_tagesarbeit_erledigt; navigate zu almhausen.php op=ernst_tagesaufgabe)
  • patches/current/seed_dialog_klara_t8.php: Dialog-Tree klara_frost/kampfkunst (T-8 — Kampfkunst erklären + navigate training.php op=kampfkunst_lernen; Meisterkampf navigate training.php op=meisterkampf; nodes für Wiederkehr + Philosophie-Fragen)
  • patches/current/seed_dialog_helga_t9.php: Dialog-Trees helga_brandt/baurecht + helga_brandt/baurecht_geschafft (T-9 — Erstkontakt + 3-Waldkämpfe-Anforderung; nach Abschluss: give_item baurecht_almhausen + holzbalken×2 + eisennagel + give_exp + milestone erstes_haus_almhausen; set_flag lena_tutorial_ende_freigegeben)
  • patches/current/seed_dialog_lena_ende.php: Dialog-Tree lena_almhausen/tutorial_ende (Tutorial-Ende — "Du bleibst also. Dachte ich mir fast."; navigate zu almhausen.php op=kutsche; set_flag reisesystem_freigeschaltet)
  • patches/current/seed_items_tutorial.php: Neue Items rattenfell + giftzahn (Keller-Loot, stackable, nicht kaufbar) + baurecht_almhausen (document, keep_on_dragonkill=1); Milestone-Definition erstes_haus_almhausen

Flag-Kette der Tutorial-Dialoge (Referenz für almhausen.php-Implementierung)

PHP-Ops die noch implementiert werden müssen (in almhausen.php):

  • op=zimmer_mieten — Gold abziehen, flag zimmer_gemietet setzen, in Zimmer redirecten
  • op=essen_kaufen — Gold abziehen, HP-Buff anwenden
  • op=schwarzes_brett — Brett anzeigen (optional, navigate-Fallback)
  • op=keller — Keller-Kampf initiieren (Tutorial-Ratte)
  • op=marta_tausch — rattenfell + giftzahn nehmen, klassenspezifische Starterausrüstung geben
  • op=ernst_waldraeuber — Waldräuber-Kampfsequenz (2–3 Gegner)
  • op=ernst_tagesaufgabe — tägliche Mühlen-Aufgaben anzeigen
  • op=kutsche — Reise-Interface öffnen

Almhausen Tutorial-Quest-Kette (T-2 bis T-9) — Quest-System-Integration

  • dialog.php: Neuer Effect-Typ take_gold — zieht Gold atomar via Character::tryDeductGold() ab; loggt Insuffizient-Fälle in security_log; Docblock aller Effect-Typen ergänzt.
  • ajax/combat_ajax.php: Kill-Tracking nach Kampfsieg — Feind-Slugs werden vor 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.
  • almhausen.php: 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).

Neue Seed-Dateien

  • seed_items_almhausen.php: Items: rattenfell + rattenzahn (quest_item, Waldloot), starter_waffe + starter_ruestung (Quest-Rewards T-5), almh_heilkraut (kaufbar 80G), bauernsegen_amulett (200G), konrads_siegelwachs (10G), flusssteinstaub (150G).
  • seed_creatures_almhausen.php: Kreatur riesenratte_keller (lv1, respawn_type=once, Loot 100% rattenfell+rattenzahn). Kreatur waldraeuber (lv2, spawn almhausen_bach + forest_almhausen, spawn_weight=8).
  • seed_npcs_almhausen.php: NPC-Profiles (npc_descriptions) — garrik, hilde_kamm, marta_brey, ernst_sack, klara_frost, helga_brandt, lena_weg, milla (alle ON DUPLICATE KEY UPDATE).
  • seed_quests_almhausen.php: 10 Quests der Kette almhausen_tutorial (alm_t2 bis alm_t9, inkl. Parallel-Quests t7a/t7b/t7c) mit korrekten sort_order, prereq, objectives JSON (kill/collect/deliver/flag), rewards JSON (exp/gold/items/flags/milestone).
  • seed_dialog_milla_dorfplatz.php: Dialog-Tree milla/dorfplatz_szene — Einmalige Dorfplatz-Szene nach Stadtanmeldung: Garrik-Einwand-Subkette, endet mit give_gold 80 + set_flag dorfplatz_szene_gesehen + give_quest alm_t2_erste_nacht.
  • seed_dialog_hilde_schenke.php: Dialog-Tree hilde_kamm/schenke_almhausen (vollständiger Ersatz) — T-2/T-3/T-4/T-7b Quest-Choices mit Multi-Condition; take_gold-Effekte für Zimmer (10G) und Essen (5G); T-3 Brett-Lesen mit complete_quest alm_t3 + give_quest alm_t4.
  • seed_dialog_ernst_muehle.php: Dialog-Tree ernst_sack/muehle_almhausen (vollständiger Ersatz) — T-6 Quest-Accept/-Complete, T-7c Quest-Accept/-Complete mit quest_done/quest_not_done Conditions.
  • seed_dialog_marta_markt.php: Dialog-Tree marta_brey/markt_almhausen (vollständiger Ersatz) — T-5 Tausch mit has_item + quest_active Conditions; complete_quest alm_t5_markt (nimmt rattenfell+rattenzahn automatisch via deliver-Objective); give_quest alm_t6_muehle.
  • seed_dialog_konrad_t7a.php: Liest konrad_fels/rathaus_almhausen aus DB, fügt T-7a-Nodes (t7a_angebot + t7a_abgabe) + Choices am Intro-Anfang ein; quest_done alm_t6 + quest_not_done alm_t7a als Condition.
  • seed_dialog_klara_t8.php (ergänzt): Neuer Tree klara_frost/klara_t8 vorangestellt (Quest-integriert); Legacy-Tree klara_frost/kampfkunst bleibt unverändert erhalten.
  • seed_dialog_helga_t9.php (ergänzt): Neuer Tree helga_brandt/helga_t9 vorangestellt (Quest-integriert); Legacy-Trees helga_brandt/baurecht + baurecht_geschafft bleiben unverändert erhalten.

Abhängigkeiten / noch offen

  • T-4 (riesenratte_keller): op=keller ist Stub — braucht CombatSession::start() mit once-Kreatur und Redirect zu combat.php
  • T-6/T-7c Kill-Objectives: progressKill läuft, aber Waldräuber müssen als Kreatur in combat_sessions auftauchen (Spawn-System)
  • T-7a/T-7b Collect-Objectives: mondblume/kleeraupe brauchen Loot-Drops aus creature_loot_tables
  • Lena tutorial_ende Dialog: braucht Hausbau-Trigger (noch nicht implementiert)

Was

  • Quest-Benachrichtigung: Banner + Topbar-Blink wenn neue Quest erhalten
  • Admin Grotte: Quest-Trigger (alle Quests starten/testen mit Animation + Redirect)
  • admin_patch.php: Mini-Update-Pack (op=pack_mini) — Kategorien wählen → ZIP herunterladen
  • admin_patch.php: Seed exportieren (op=export_seed) — quest_definitions + dialog_trees aus DB → PHP-Seed
  • admin_patch.php: Selektiv installieren (op=selective) — ZIP hochladen, nur ausgewählte Kategorien deployen
  • admin_patch.php: Berechtigungs-Fix (op=fix_perms in admin_grotte.php) — chmod + chown-Befehl
  • patches/v0.1.1/patch.sql: Idempotenz-Fix (DROP INDEX IF EXISTS / ADD UNIQUE KEY IF NOT EXISTS)

Warum

  • Quest-Feedback für Spieler (visuelle Bestätigung)
  • Testbarkeit der Quest-Flows ohne Live-Spielfortschritt
  • Einfachere Partial-Updates auf Testserver (nur Dialoge/Quests/CSS patchen)
  • Seed-Export ersetzt manuelles Kopieren von JSON aus der DB
  • Permission denied auf Linux-Server behoben

[0.1.1] – 2026-05-22

Was

  • almhausen.php: Dorf Almhausen — Dorfplatz + 7 Gebäude (Rathaus, Schenke, Mühle, Markt, Schmiede, Kutsche, Gasse); Speisekarte mit 5 Gerichten (Buff/HP/Turns); Markt mit 4 Items via Item::shopBuy; Kutsche nach Edahnien; city-Tracking; Heimatbonus-Hinweis für Menschen (race=1); tageszeit-abhängige Flavor-Texte
  • patches/current/seed_almhausen_npcs.php: 7 NPC-Beschreibungen (npc_descriptions) + 7 Dialog-Bäume (dialog_trees) für alle Almhausen-NPCs; Siegbert location_slug aktualisiert
  • patch.sql: 4 neue item_definitions für Almhausen-Markt (almh_heilkraut, bauernsegen_amulett, konrads_siegelwachs, flusssteinstaub)
  • rathaus.php: Emojis aus Navigation entfernt (📋🏆⚖💰🎖✨🔨📊⛓🔮🚪)
  • forest.php: Waldhütte (op=huette) — Heilung gegen Gold implementiert; Kosten per Setting forest_heal_cost_per_hp (Standard: 3 Gold/LP); Content-Strings 10–17
  • forest.php: Waldhütte nutzt jetzt DialogSystem (NPC Marta); op=huette_heilen als schlanke Rückgabeseite
  • DialogSystem::choiceAllowed() — hp_not_full, hp_full, gold_min Conditions ergänzt
  • NPC Marta (marta/waldhuette) — npc_descriptions + dialog_trees eingespielt via seed_dialog_marta.php
  • DB-Cleanup: 73 legacy-Tabellen gelöscht (_del_*, accounts, pvp, bans, nastywords, petitions, masters, armor, weapons, motd, ...)
  • characters: banned_until + ban_reason Spalten ergänzt (ersetzt bans-Tabelle)
  • Auth.php: checkBan() nutzt jetzt characters statt bans-Tabelle
  • User.php: banIp() entfernt (schrieb in bans-Tabelle)
  • login.php: banIp()-Aufruf bei Brute-Force entfernt
  • setnewday.php + dragon.php: pvp DELETE-Statements entfernt
  • output_func_inc.php: soap() Wordfilter entfernt (nastywords-Abhängigkeit weg)
  • appoencode → bbcodes (type='backtick'): TextRenderer.php + seed_bbcodes_backtick.php
  • bbcodes.tag: Collation → utf8mb4_bin (case-sensitive für Backtick-Codes a/A etc.)
  • Router::addLink/addHeading/addHelp/addButton: rufen jetzt intern TextRenderer::chat() auf
  • admin_patch.php: MasterBuilder-Feature (op=master) — kumulatives Deployment-Paket
  • admin_patch.php: Chunked Upload (op=upload_chunk) — ZIP-Dateien > 4 MB werden in 2-MB-Chunks hochgeladen; processZipFile() extrahiert als gemeinsame Hilfsfunktion
  • MasterBuilder::buildSchema(): überspringt jetzt _del_*-Tabellen, VIEWs und SKIP_SCHEMA_TABLES; MODIFY_COLUMNS-Konstante für ENUM-Änderungen (bbcodes.type + backtick); SHOW FULL TABLES statt SHOW TABLES
  • MasterBuilder: scene_hotspots + scene_pages zu GAMEDATA_TABLES ergänzt (werden künftig im Master-Deploy synchronisiert)
  • common.php navExempt: shop_ajax, chat_ajax, equip_ajax, display_settings_ajax, combat_ajax, dialog_speed ergänzt (fehlten → Rüstkammer-Shop "Verbindungsfehler")
  • item_definitions: Slot-Namen vereinheitlicht — helm→kopf, handschuhe→arme, schultern→schulter (stimmten nicht mit Puppet-Slot-IDs überein → Ausrüsten/Drag&Drop schlug lautlos fehl)
  • dlg-system.js: setInnerHtmlExec() Helper — führt <script>-Tags nach innerHTML-Zuweisung aus; ivSetupDnd + IV_DATA_* wurden nach Inventar-Refresh nicht mehr ausgeführt → Drag & Drop kaputt
  • Character.php: clearEquipmentBonusCache() — löscht equipBonusCache nach Equip/Unequip damit effectiveAttack/Defence/Initiative frische Werte liefern
  • WizardHelper.php: RACE_CITY Konstante — Rasse → Startstadt-Mapping (mensch→almhausen, alle anderen→edahnien als Fallback bis Städte implementiert)
  • wizard.php: Startstadt beim Char-Save aus RACE_CITY gesetzt (->set('city', $startCity))
  • wizard.php + templates/css/landing.css: Heldenausweis auf absolut-positioniertes Layout umgestellt (Koordinaten aus ausweis_preview.html per Drag-Tool kalibriert); Story/Siegel/Unterschrift im Wizard ausgeblendet — werden erst im Ärztin-Dialog nach Klassenwahl befüllt; ausweis_preview.html als Standalone-Vorschau für weiteres Feintuning
  • wizard.php + templates/css/landing.css: Ärztin-Dialog implementiert — nach dem Intro-Ablauf erscheint NPC "Milla" (Ärztin) in einem Panel neben dem Heldenausweis; Typewriter-Dialog mit 3 Zeilen + Portrait-Wechsel-Option + Bestätigung; _aeztinFinish() führt Schwarzblende → wizGoto(3) aus; für op=reset deaktiviert (kein Panel, keine JS-Logik)
  • equip_ajax.php: gibt nach equip/unequip char_stats {att,def,ini,cha} zurück (Server-seitig frisch berechnet)
  • Item.php: ivEquipItem() aktualisiert .iv-pds-val-* Spans aus res.char_stats nach Equip
  • Item.php: data-equipped="1" + data-slot Attribute auf Karten; ivEquipItem() setzt/entfernt data-equipped nach Equip
  • Item.php: Ausrüstungs-Unterfilter — ivSubFilter(uid, slot), Sub-Filter-Leiste (Alle/Kopf/Brust/…) erscheint bei Kategorie "equip"
  • edahnien.css: Grüner Rahmen + E-Badge für data-equipped="1"; Styling für .iv-subfilter + .iv-subcat
  • newday.php: HP wird nach Newday wieder auf maxHitpoints gesetzt (fehlte bisher)
  • edahnien.css: battle-nav span.nav:not(.dim) — Hover-Stil ergänzt (Buttons sahen aus wie disabled)
  • combat.php: enableButtons(true) nach ongoing-Runde (fehlte → Nav-Buttons blieben nach 1 Runde dauerhaft dim)
  • combat.php: showEndScreen() neu — benutzt jetzt combat-end-glyph/title.v/.d, combat-loot-box/row/gl/q, combat-cta-row/btn statt den alten Klassen (screen entsprach nicht der Vorlage)
  • CombatService.php: strukturierte Log-Einträge {text, type} statt Klartext — Typen: player/enemy/crit/good/status/system
  • CombatService.php: Ausweichen — je 4% pro INI-Punkt Vorsprung des Spielers, max 40% (kein Schaden)
  • CombatService.php: Parieren — je 3% pro DEF-Punkt über Gegner-ATK, max 35% (kein Schaden + sofortiger Gegenschlag mit 50% ATK)
  • combat.php: logAppend() akzeptiert {text,type}-Objekte; Marker ⚔/◆/✦/☠/· je Typ
  • edahnien.css: .clog-line.clog-player (grün), .clog-enemy (rot), .clog-crit (gold), .clog-good (gold), .clog-status (lila), .clog-system (dim/italic)
  • templates/layout/combat.php: End-Screen — Waldkulisse eingebunden (combat_forest_bg.php, id='e'); "Zurück"-Button aus Header entfernt; Level-Up-Zeile aus Loot-Box entfernt (wird im Spiel später via Training aufgelöst)
  • edahnien.css: combat-end-glow/content z-index korrigiert (liegen jetzt über dem Waldbild)
  • combat.php: End-Screen CTAs — "Weiter kämpfen" → forest.php?op=kampf (sofort neuer Kampf), "Ins Dorf"→"In den Wald" → forest.php; Niederlage-Screen analog
  • forest.php / settings: forest_heal_cost_per_hp 3 → 1 Gold/LP (Lv1-Kämpfe geben ~5 Gold; bei 3 Gold/LP kostete Heilen 5 LP = 15 Gold = 3× Kampfgewinn — zu teuer)
  • common.php: cityUrl($city) Hilfsfunktion — gibt richtige Dorfplatz-URL je Stadt zurück (almhausen→almhausen.php, edahnien→village.php?city=edahnien, …)
  • 15 aktive Seiten auf cityUrl() umgestellt (acclogin, newday, bank, burgschmiede, burgviertel, bibliothek, galgen, houses, inn, kutschenhof, markt, rathaus, schlosspark, tempel, training) — 21 Stellen total
  • village.php: Almhausen-Link im Admin-Block (nur für Superuser ≥ 1 sichtbar)
  • almhausen.php: admin_grotte.php zur Whitelist ergänzt; Edahnien-Link + Admin Grotte im Admin-Block (nur für Superuser ≥ 1 sichtbar)
  • admin_grotte.php: Rücklink + Zugangsschutz-Redirect nutzen cityUrl($character->city()) — Spieler landen nach der Grotte in der Stadt wo sie zuletzt waren, nicht immer in Edahnien
  • village.php: cala/arvika/lushanja aus $validCities, $cityTitleIds, $cityTitleFallbacks, $introIds entfernt — diese Städte kommen nicht zurück; neue Städte (Almhausen etc.) bekommen eigene PHP-Dateien
  • CombatService::applyWin(): Heimatbonus — Menschen (race=1) in Almhausen erhalten +5% EXP (mind. +1); heimat_bonus-Feld im Loot-Rückgabe-Array
  • combat.php: Sieg-Loot-Box zeigt Heimatbonus-Zeile (🏠 + XP-Wert) wenn heimat_bonus gesetzt
  • DialogAjax::requireAmount(): $max=0 wurde als "kein Limit" behandelt (0 > 0 = false → Max-Check übersprungen) → Spieler mit 0 Gold/Edelsteinen konnten negative Bestände erzeugen; Fix: $max=-1 als "kein Limit"-Sentinel, 0 wird jetzt als echtes Maximum durchgesetzt
  • bank.php: Explizite requireCondition-Vorab-Checks für alle 4 Transaktionen (Gold einzahlen/abheben, Gems einlagern/herausholen) — klare Fehlermeldung wenn Betrag = 0 statt stiller Bypass
  • combat.php: "Weiter kämpfen"-Button im Sieg-Screen wird ausgeblendet wenn turns=0 — kein toter Link mehr
  • races: visible-Spalte — visible=false+active=false → nur Editor; visible=true+active=false → Wizard "Coming Soon" (nicht wählbar); active=true → normal; Race::findForWizard() + WizardHelper::loadRaces() + wizard.php JS + admin_races.php (toggle_visible + Formular-Checkbox)
  • wizard.php + edahnien.css: Intro-Overlay vor dem Wizard — fullscreen Video (templates/assets/intro/intro.mp4, optional), Schleier-Überblendung, NPC-Typewriter-Dialog ("Du erwachst… Kannst du dich noch an deinen Namen erinnern?"), Namenseingabe inline, nach Bestätigung fade → Wizard ab Schritt Geschlecht; Überspringen-Button; nur für neue Charaktere (isReset=false)
  • Race::findForWizard(): Fallback auf findActive() wenn visible-Spalte noch nicht existiert (DB-Migration noch nicht eingespielt → fetchAll gibt [] zurück → Wizard explodierte mit "Undefined array key 0")
  • WizardHelper::collectData(): null-coalescing auf $raceKeys[0]/$klassKeys[0]/$styleKeys[0] — defensiv gegen leere Listen
  • docs/techdemo_appearance.html: gelöscht — Ansatz verworfen; Portraits werden stattdessen als fertige Assets erstellt und über Event/Premium-Shop verfügbar gemacht (ggf. KI-Generierung via Gemini)
  • edahnien.css: .wiz .cs-head z-index: 5 → 10001 — Wizard-Header bleibt immer über dem Intro-Overlay sichtbar (Overlay z-index: 9999, Header z-index: 10001, isolate-Stacking-Context in .wiz)
  • wizard.php: $isReset in Schritt-Aufbau-Block undefiniert → $isNewChar ($charId === 0) eingeführt; $hasIntroVideo und Intro-Overlay-Bedingung nutzen jetzt $isNewChar statt $isReset
  • edahnien.css: Wizard-Intro visuelles Overhaul — appearance:none + color-scheme:dark + background:transparent!important für Input (Browser-Defaults deaktiviert); radialer dunkler Halo als ::before auf .wiz-intro-dlg (Lesbarkeit über Hintergrundbild); Trennlinie unter NPC-Label; .wiz-intro-btn als flex-center; .wiz-intro-veil transition direkt in CSS statt per JS
  • wizard.php + edahnien.css: Intro-Overhaul Phase 2 — 4-Phasen-Flow: Video→NPC-Dialog (Linke, schwarz)→Augen-Blink-Animation (Hintergrund taucht verschwommen auf)→Namens-Frage; .wiz-intro-bg mit Hintergrundbild für Blink-Phase; Schritt-Counter via body.wiz-intro-active ausgeblendet; _wizPhase-Tracking; skip springt direkt zu Frage
  • wizard.php: $isNewChar ($charId === 0) → ($op === 'new') — Intro zeigt jetzt für alle op=new-Aufrufe (auch für bestehende Chars beim Testen); WIZ_IS_RESET basiert jetzt auf $op==='reset' statt $charId>0
  • templates/layout/interactive.php + edahnien.css: #wiz-intro position:fixed → position:absolute — Overlay als direktes .wiz-Kind (vor dem Header); .wiz ist position:relative, min-height:100vh → Overlay inset:0 = exakt fullscreen; Header (z-index:10001) im selben Stacking-Context über Overlay (z-index:9999); position:fixed innerhalb von overflow:hidden+isolation:isolate wurde von Chrome falsch behandelt (Dialog im Nirgendwo, Veil-Abdunklung kaputt)
  • wizard.php: Intro-Audio — Single-File Timestamp-Modus (INTRO_AUDIO_SRC + t-Werte in INTRO_LINES); introTimestampMode() treibt Dialog über timeupdate-Events; sequentieller Fallback wenn t=null; introSkip() stoppt Audio; "Linke"-Speaker-Label entfernt; wi-dlg blendet in Phase 3 aus; Blinks langsamer (hold: 700/950/1300ms)
  • wizard.php: Komplett-Rebuild — 7 Einzelschritte → 3 kombinierte Schritte (Identität / Kampfkunst / Bereit); saubere JS-Architektur mit wizState-Objekt; Intro-Modul mit introVeil()-Reflow-Fix (v.offsetHeight zwischen transition und opacity-Zuweisung); $isNewChar = ($op === 'new') damit Intro für alle op=new-Besuche erscheint (nicht nur bei charId=0); WIZ_IS_RESET = ($op === 'reset')
  • edahnien.css: Neue Layout-Klassen für Wizard-Rebuild — .wiz-step, .wiz-name-row, .wiz-main-row, .wiz-portrait-col, .wiz-controls-col, .wiz-section-label, .wiz-style-desc, .style-cards, .wiz-variant-nav, .wiz-variant-label, .wiz-arrow.small, .gender-card .gender-icon, .style-card .style-icon/.style-name; .summary neu als 2-Spalten-Grid; responsive Breakpoint @media (max-width: 860px)
  • wizard.php + landing.css: Wizard-Flow-Redesign — Dialog-basierte Charaktererstellung; Gender/Rasse/Klasse/Kampfstil werden jetzt inline im Intro-Dialog als anklickbare Choices abgefragt (Phasen 4–7); Barbar+Amazone aus Rassenangebot entfernt; Andere Rassen als "Demnächst" gesperrt sichtbar; Step 1 ist jetzt nur noch Avatar-Auswahl (Portrait + Varianten); Step 2 (Klassen-Karussell) entfernt; Appearance-Panel (Haut/Haar/Augen) entfernt; Geschlecht-Karten entfernt; 2-Step-Fortschrittsbalken (Avatar → Bereit); introPhase-Tracking auf 8 für Dismiss; introSkip() springt bei Phasen 4–7 direkt zu Dismiss
  • wizard.php + landing.css: Dialog-Texte überarbeitet — "Und... du bist?" (Geschlecht), "Was bist du?" (Rasse), "Welche Waffe bevorzugst du?" (Kampfstil); Alle Rassen außer Mensch fix gesperrt (nicht nur coming_soon); nicht wählbare Klassen ausgeblendet (statt gesperrt angezeigt); letzte 2 INTRO_LINES entfernt ("Ich weiß dass du mich hörst" / "Kannst du mich hören?"); Ohnmacht-Animation nach "Sehr gut." (veil→schwarz + page-veil Aufwach-Effekt); Portrait-Picker: PHP scannt templates/assets/characters/Portraits/{race}/{gender}/ (ohne _premium), JS rendert Thumbnail-Grid; WIZ_PORTRAITS const; wizRenderPortraitPicker() + wizSelectPortrait()
  • wizard.php: Steckbrief-Hintergrundpfad korrigiert → templates/assets/backgrounds/steckbrief.png (war wizard/steckbrief_bg.png); background-image direkt als Inline-Style (nicht CSS-Variable) da url() in Custom Properties relativ zur CSS-Datei aufgelöst wird
  • newday.php: HP-Restore auf echtes Maximum — vorher nur maxHitpoints() (DB-Rohwert), jetzt maxHitpoints() + dragonHpBonus() + Buff::statBonus('maxhp') wie charstats_func_inc.php; $effectiveMaxHp auch als Cap für Wetter-Bonus und Haus-Komfortbonus
  • wizard.php: introSkip() — Video wird jetzt pausiert + display:none gesetzt (lief bisher nach dem Überspringen weiter)
  • wizard.php: introDismiss() — Aufwach-Animation fixiert: Video/Audio stoppen; pv.offsetHeight-Reflow + doppeltes requestAnimationFrame statt setTimeout(120) → Browser committed opacity:1 sicher bevor die Fade-Transition startet (vorher blieb der Bildschirm dauerhaft schwarz)
  • landing.css: Steckbrief-Karte komplett neu — aspect-ratio 1792/2400, background-size 100% 100%; Portrait absolut positioniert bei left 13.95% / top 10.42% / width 24.27% (entspricht Rahmeninnenbereich der Grafik); kein CSS-Rahmen mehr (Grafik bringt eigenen Ornament-Rahmen); Info-Spalte absolut ab left 43%; wsb-portrait-frame border entfernt
  • ausweis_preview.html: Standalone-Preview für Heldenausweis (kein Intro, Toolbar: Name/Rasse/Gesch./Klasse/Waffe/Größe, Overlay-Toggle, Story-Blur-Toggle)
  • wizard.php + landing.css: Steckbrief → Heldenausweis-Redesign — neue .id-card Komponente (dunkel-grüner Hintergrund, Ornament-Rahmen); Header-Leiste "✦ HELDENAUSWEIS ✦"; Name groß in Cinzel; Titel-Zeile "✦ der Namenlose ✦"; Body 2-Spalten (Portrait links, Daten rechts); idc-dl zeigt Rasse/Geschlecht/Klasse/Waffe; Lebensgeschichte als unlesbarer Dancing-Script-Blur-Platzhalter; Footer mit Dancing-Script-Unterschrift (= Charname) + SVG-Siegel (Farbe + Buchstabe je Rasse); wizUpdateSeal() aktualisiert Siegel dynamisch; wsb-gender-Feld neu; base.css: Dancing Script zu Google Fonts hinzugefügt
  • wizard.php: Ärztin-Dialog als normale Seite (op=ausweis) — Intro-Dismiss ruft jetzt direkt wizSave() auf; op=save leitet neue Chars zu wizard.php?op=ausweis statt acclogin; op=ausweis ist normales info-Template mit DialogSystem::render('milla','ausweis_wizard') + server-seitig gerendertem Heldenausweis (inkl. Unterschrift-Linie ohne Inhalt + Siegel) + leichtgewichtigem Portrait-AJAX; op=finish leitet zu acclogin?op=logedin; op=abandon löscht Char (Token+user_id+level≤1 geprüft) + redirect acclogin; op=save_portrait speichert appearance_variant via AJAX; Step-3-Zusammenfassung + Milla-Panel-HTML entfernt; wiz-save-form ausgelagert (immer gerendert, auch für op=reset); AEZTINJS-Block entfernt
  • patches/current/seed_milla_wizard.php: navigate_js komplett entfernt; switch_portrait hat direkte choices; neuer abschied-Node mit navigate:'wizard.php?op=finish'; portrait_confirm-Node entfernt
  • landing.css: Custom-Inner-Styles (.aerztin-npc-header, .aerztin-body, .aerztin-text, .aerztin-choices, .aerztin-choice-btn) entfernt; .aerztin-panel als schlanker Wrapper; .dlg-controls ausgeblendet
  • wizard.php + seed_milla_wizard.php: Flow-Redesign — nach op=save Redirect zu wizard.php?op=ausweis statt direkt zu acclogin; op=ausweis zeigt Heldenausweis (server-seitig gerendert) + DialogSystem::render('milla', 'ausweis_wizard') nebeneinander im info-Template; op=finish leitet zu acclogin?op=logedin weiter; op=abandon löscht neuen Char + leert Session; op=save_portrait AJAX speichert appearance_variant; introDismiss() ruft direkt wizSave() auf (kein wizGoto(1)/aeztinStart mehr); AEZTINJS-Block entfernt; Step-3-Zusammenfassung entfernt; $millaDialogData-Block entfernt; seed_milla_wizard: navigate_js durch navigate ersetzt, Fallback portrait_confirm entfernt, neuer abschied-Node mit navigate→wizard.php?op=finish
  • DialogSystem::render() + build(): $extraContent-Parameter ergänzt — optionaler HTML-Block der zwischen .dlg-stage und <hr class="dlg-divider"> injiziert wird; wizard.php op=ausweis baut Heldenausweis als PHP-String und übergibt ihn via $extraContent → Ausweis erscheint jetzt physisch innerhalb des Dialog-Widgets statt darunter
  • edahnien.css: .dlg-panel .id-card — width min(260px, 82%) + margin auto (Ausweis im Dialog-Panel auf kompakte Größe skaliert)
  • dlg-system.js: on_show-Hook — Nodes können "on_show": "fnName" deklarieren; show() feuert window.fnName(uid) unmittelbar wenn der Node beginnt zu rendern
  • dlg-system.js: next-Property — Nodes ohne choices können "next": "nodeKey" setzen; nach dem Typewrite-Ende wird 900ms gewartet und dann automatisch weitergesprungen (kein "…"-Button nötig)
  • dlg-system.js: Spieler-av-wrap wird in dlgInit() ausgeblendet wenn data.player.avatar leer ist (kein kaputtes Bild + kein Name-Label)
  • seed_milla_wizard.php: intro_1/2/3 nutzen jetzt "next" statt choices:['…']; Dialog läuft automatisch durch; intro_2 nennt Waldrand + Kampf gegen Grünen Drachen; intro_3 verweist auf Tage im Lazarett; ask_portrait-Node erhält on_show:'ausShowCard'
  • wizard.php op=ausweis: id-card startet display:none + opacity:0 + translateY(10px); ausShowCard() blendet sie mit CSS-Transition ein sobald ask_portrait-Node gezeigt wird
  • Output.php: hideSidebar() — unterdrückt $charstats in render(); wizard.php op=ausweis ruft Output::hideSidebar() auf → Stats-Sidebar im Wizard komplett ausgeblendet
  • wizard.php op=ausweis: ausUpdatePortrait() aktualisiert jetzt auch .dlg-avatar--player img-Src synchron wenn Portrait gewechselt wird
  • charstats_func_inc.php: Portrait-Logik auf neues Portraits-System umgestellt (appearance_variant + Portraits/{race}/{gender}/ wie wizard.php); $hasAvatar nur true wenn Datei tatsächlich existiert (file_exists); Portrait-Div wird komplett weggelassen wenn kein Bild vorhanden — kein kaputtes Bild mehr in der Vitalinfo-Sidebar
  • wizard.php op=finish: Bugfix Logout nach Wizard — acclogin?op=logedin Umweg entfernt; Char ist nach op=save bereits eingeloggt (loggedin=1 in DB + Session); op=finish leitet jetzt direkt per cityUrl() ins Dorf weiter + befüllt session['user2'] für Legacy-Kompatibilität
  • common.php navExempt: acclogin.php ergänzt — war nicht gelistet, wodurch acclogin?op=logedin ohne Whitelist-Eintrag auf badnav landete (Sicherheitsmodell: Token-Verifikation via findByToken() bleibt als eigene Auth-Schicht erhalten)
  • acclogin.php needsWizard: klasse() → specialty() — Wizard schreibt specialty (via $klassMap), klasse-Spalte wird NIE gesetzt; needsWizard war dadurch für alle Wizard-Chars immer true → nach Logout landeten Spieler auf index.php statt im Spiel; Redirect geändert von create.php?op=wizard (→ index.php) direkt zu wizard.php?op=new
  • acclogin.php case logedin: Router::allow('login.php?op=logout') + Router::allow('login.php?op=charlogout') explizit gesetzt (Fallback wenn Char nicht per Token gefunden wird und die Seite gerendert wird)
  • common.php noRestore: index.php, login.php, register.php, reset.php ergänzt — restorepage darf nie auf eine öffentliche/Auth-Seite zeigen (würde Spieler nach Timeout auf die Landing Page statt ins Spiel zurückleiten)
  • login.php: E-Mail-Validierungs-Check jetzt abhängig von Setting require_email_validation (Standard 0 = deaktiviert); verhindert Login-Blockade auf lokaler Entwicklungsumgebung ohne Mailserver
  • register.php: bei require_email_validation=0 wird Konto sofort aktiviert (emailvalidation='yes') statt Validierungs-Token + mail() zu senden; anderer Erfolgs-Text (textid 44) ohne E-Mail-Hinweis
  • patch.sql: require_email_validation Setting (Standard '0') + content_strings register.php textid 44
  • newday.php needsWizard: klasse() → specialty() + kampfkunst() ergänzt (selbe Logik wie acclogin.php); Redirect von create.php?op=wizard (→ index.php) auf wizard.php?op=new korrigiert — Neulinge wurden nach newday ins Nirgendwo geschickt statt zum Wizard
  • acclogin.php: Admin-Delete-Button für Nicht-Admin-Chars — renderCharSlot() hat neuen $showAdminDelete-Parameter; op=admin_delete_char löscht Char + character_buffs/milestones/quests/items/chat_messages; Button nur sichtbar wenn Viewer-Account einen Admin-Char hat; Admin-Chars selbst sind nie löschbar (superuser>0-Guard); JS confirm() vor Redirect
  • templates/edahnien.css: .cs-foot-actions + .cs-delete-btn Styles
  • characters: city-Spalte (VARCHAR 50, DEFAULT '') ergänzt — fehlte komplett → wizard op=save schlug mit DB-Fehler fehl (city wurde nicht gespeichert → alle Spieler landeten in Edahnien statt Startstadt)
  • wizard.php op=finish: Quest::accept('start_rathaus') für neue Charaktere (nur wenn status='none'); Router::allowPrefix('almhausen.php') ergänzt
  • WizardHelper::collectData(): alte skin/hair/eyes/hairStyle/beard-Felder + undefined $defaultSkin/Hair/Eyes/HStyle/Beard entfernt; $defaultAvatar als Fallback für variant
  • patches/current/seed_quest_start.php: Starter-Quest 'start_rathaus' (Anmeldung beim Bürgermeister, Belohnung 50G+25XP)
  • appearance_* → avatar: appearance_skin/hair/eyes/hairstyle/beard aus DB + Code entfernt; appearance_variant → avatar (TINYINT, Portrait-Index); Character::portraitPath() als zentrale Portrait-Pfad-Methode; charstats_func_inc, DialogSystem, wizard, acclogin, combat, Item (2 Stellen), Chat (SQL + buildAvatarInner) und WizardHelper aktualisiert; patch.sql: ADD avatar + DROP appearance_*
  • Quest-Notification: "Neue Quest erhalten"-Banner (zentriert, goldener Rahmen, 2.8s sichtbar) + Topbar "Profil & Inventar"-Button blinkt golden (6× Pulse); Session-Pending-Flag via Quest::accept() → Output::render() → info.php-Template; Topbar-Button id="topbar-btn-inventar" + onclick entfernt Blink-Klasse; layout.css: #quest-banner + .qb-* + .topbar-btn-notify + @keyframes qb-enter/qb-leave/topbar-quest-pulse

Warum

[0.1.0d] – 2026-05-19

Was

  • admin_patch.php: Crash behoben wenn patches/current/patch.md oder files.txt auf dem Server fehlen
  • markt.php + templates/layout/scene.php auf Testserver fehlen (v0.0.8 + v0.1.0 nicht deployed)
  • seed_markt_dialogs.php: rustkammer/markt + gareth/markt Dialog-Trees fehlen auf Testserver

Warum

[0.1.0c] – 2026-05-19

Was

  • admin_patch.php: op=pack_code — Code-only ZIP ohne Bilder (für Server mit kleinem Upload-Limit)
  • admin_patch.php: buildPatchZip() nutzt addFile() statt addFromString() — kein Memory-Exhausted
  • CSS-Pfad-Fix: url('assets/...')url('../assets/...') in landing.css + combat.css
  • common.php: GAME_VERSION Fallback-Konstante auf '0.1.0b' aktualisiert (war '0.0.7e')
  • impressum_link.php: Neudeployment — stellt sicher dass Server die Version aus DB liest (getsetting) statt alter Hardcodierung
  • adminmodule/admin_charedi.php + creatures + hotspots + milestones: URL-Bug ?module=X?op=Y?module=X&op=Y — Links in Admin-Modulen landeten immer in der Grotte
  • patch.sql: kumulatives SQL von v0.0.8–v0.1.0b (ALTER TABLE, Seeds, Versionsnummer)

Warum

  • Server war auf v0.0.7 stehengeblieben — SQL-Lücke von 4 Patches
  • Server zeigte v0.0.7 statt v0.1.0b: impressum_link.php auf Server war alte Version (vor Settings-Integration)
  • Admin-Module: zweites ? statt & als Query-Parametertrenner → PHP las module=charedi?op=view als Module-Name, kein Match in moduleMap → Redirect zur Grotte
  • Bilder (8MB/Stück, 323MB gesamt) nicht per Upload deploybar → Code-only ZIP als Workaround
  • Bilder müssen separat optimiert (pngquant/TinyPNG) + per SFTP/FTP deployed werden

[0.1.0b] – 2026-05-19

Was

  • admin_patch.php: buildPatchZip() nutzt jetzt addFile() statt addFromString() → kein Memory-Exhausted bei Bildern
  • CSS-Pfad-Fix: url('assets/...')url('../assets/...') in landing.css + combat.css
  • Alle templates/assets/ Bilder in files.txt eingetragen (waren nie deployed)

Warum

  • Titelbild fehlte auf Testserver: dragon-keyart-green.png wurde nie deployt + CSS-Pfad war falsch
  • Alle POI-Hintergrundbilder ebenfalls nie in files.txt → werden jetzt mitgeliefert
  • Kreatur-Portraits in templates/assets/creatures/ nie deployt → im Arachno-Modus sichtbar weil

[0.1.0b] – 2026-05-19

Was

  • CSS-Pfad-Fix: url('assets/...')url('../assets/...') in landing.css + combat.css
  • Alle templates/assets/ Bilder in files.txt eingetragen (waren nie deployed)

Warum

  • Titelbild fehlte auf Testserver: dragon-keyart-green.png wurde nie deployt + CSS-Pfad war falsch
  • Alle POI-Hintergrundbilder ebenfalls nie in files.txt → werden jetzt mitgeliefert

[0.1.0] – 2026-05-19

Was

  • Kampfsystem Phase 1: CombatSession, CombatService, combat_ajax.php
  • Kreatur-Skalierung: proportionale Skalierung (Basis × Ziel/Basis-Level)
  • Drachen-Kill-Boni: Runtime-Berechnung aus dragonpoints-Array (nicht mehr in DB-Spalten)
  • Admin-Charaktereditor: Drachenkill-Editor + Drachenpunkte-Reset
  • Vitalinfo: zeigt effektiven Angriff/Verteidigung (inkl. Rasse/Equipment/Buffs/DK)
  • Combat Debug-Panel: jade-farbig, DK-Komponente + Trait-Boni-Aufschlüsselung
  • Mobile Navigation: Drawer-Pattern (Hamburger-Button + Slide-in Menü links)
  • Responsive CSS: Breakpoints für alle 5 Seitentypen
  • Ausrüstungssystem: 140 Rüstungssets (20 Level × 7 Slots) in item_definitions geseedet
  • Gareth NPC: Schildhändler im Markt (Dialog-Baum + NPC-Beschreibung)
  • Rüstkammer: shop_source='rustkammer', Gareth: shop_source='gareth'
  • battle.php entfernt: Balancing-Daten in docs/legacy_battle_balancing.md extrahiert
  • common.php Cleanup: ~250 Zeilen totes Legacy gelöscht (borkalize, get_id, checklink,
  • require/Settings.php: Settings-Klasse mit lazy DB-Cache, ON DUPLICATE KEY UPDATE upsert,
  • require/Auth.php: Auth-Klasse mit checkBan(), isEmail() (filter_var statt Regex), requireSuperuser();
  • core/Router.php: Router::redirect() als statische Methode; common.php-Shim delegiert dorthin
  • core/Router.php: Sicherheitslisten als statische Properties + initSecurityLists() + isPublicPage() /
  • common.php: $allowanonymous / $allownonnav / $nokeeprestore durch Router::initSecurityLists() ersetzt;
  • common.php: getmicrotime() → microtime(true) (direkt); Funktion entfernt
  • common.php: e_rand() → Shim mit random_int() (CSPRNG); mt_rand()-Implementierung entfernt
  • core/Output.php: getmicrotime()-Aufruf → microtime(true)
  • require/Logger.php: Logger-Klasse (debug/system/security/serverstats); Shims debuglog/systemlog
  • require/News.php: News-Klasse (add/addFor); Shim addnews
  • dbwrapper.php: Logger.php + News.php eingebunden (Logger braucht db_query, muss früh geladen sein)
  • common.php: debuglog()/systemlog()/addnews() alte Funktionsrümpfe entfernt; analysis1()/analysis2() entfernt
  • require/GameTime.php: GameTime-Klasse mit isNewDay/isDaytime/currentSlot/slotForTimestamp/
  • dbwrapper.php: GameTime.php eingebunden
  • common.php: is_new_day/get_day/gametime/convertgametime/getgametime/getgamedate/timetotomorrow
  • require/Util.php: Util-Klasse mit ordinalNominative/ordinalAccusative/dhms;
  • dbwrapper.php: Util.php eingebunden
  • common.php: ordinal_nominative/ordinal_accusative/dhms alte Funktionsrümpfe entfernt
  • common.php: clearoutput() entfernt (kein Aufrufer); expbar() entfernt (Logik in charstats)
  • common.php: quicklogout-Block gefixt (accounts → characters, $session['user']['acctid'] statt $_SESSION-Direktzugriff)
  • common.php: $revertsession entfernt (nur gesetzt, nie gelesen)
  • common.php: setnewday-IIFE nutzt jetzt GameTime::currentSlot() statt duplizierter Slot-Berechnung
  • common.php: page_footer1 / getmount / $playermount / sql_error / output_array / grafbar entfernt
  • common.php: $commentarsystem + addundviewcommentary.php-Include entfernt (altes Kommentarsystem,
  • require/LegacySkin.php: NEU — maillink/motdlink/page_header/page_footer aus common.php extrahiert;
  • Output::setTitle(string $title) NEU — ersetzt page_header($title) auf neuen Seiten
  • Output::render(): page_footer()-Fallback entfernt (fehlende Templates erzeugen jetzt einen
  • village.php + shades.php: page_header($title) → Output::setTitle($title)
  • require/Auth.php: page_header('FREVEL!')/page_footer() → Output::setTitle()/Output::render('info')
  • common.php: get_special_var/set_special_var entfernt (Spezialevent-System wird komplett neu gebaut)
  • common.php: createarray()-Aufruf als Fallback für allowednavs entfernt (Funktion war bereits
  • common.php: $timeline_names_array, $su_editoren, $aktiv_popups entfernt (alle nie ausgelesen);
  • common.php: Sicherheitslisten bereinigt — wizard.php aus publicPages entfernt
  • common.php: Kleinreinigung — doppelter quicklogout-Kommentar, //lib-Waise,
  • common.php: $su_rang entfernt (kein Aufrufer); Tombstone-Kommentarblock (~30 Zeilen)
  • common.php: dump_item() modernisiert (void $f_debug-Echo, auskommentierter each()-Loop,
  • common.php: saveuser() auf 6 Zeilen kompaktiert
  • common.php: quicklogout raw SQL → PDO (Database::getInstance()->execute())
  • common.php: Session-Timeout-Block modernisiert (time() statt strtotime(date('c')),
  • common.php: Template-Block vereinfacht (null-coalescing, Ternary, kein String-Concat-Noise);
  • common.php: Session-Login-Block bereinigt:
  • require/Character.php: Character::saveFromSession() NEU — liest allowednavs/bufflist
  • common.php: saveuser() → 5-Zeilen-Shim der zu Character::saveFromSession() /
  • common.php: IIS-CGI-Kompatibilitätsblock entfernt (~30 Zeilen); ersetzt durch 3 Zeilen
  • common.php: lib/useronline.php-Include entfernt (useronline()-Funktion nur in _legacy/; neuer Skin hat eigene Online-Anzeige)
  • require/LegacySkin.php: page_footer()-Parameter $useronline entfernt; {useronline}-Placeholder wird immer leer ersetzt
  • common.php: vendal_TIME entfernt (nie genutzt)
  • common.php: ev_htmlentities() / ev_htmlspecialchars() entfernt (beide No-Ops); Aufrufe in
  • common.php: register_global($_SERVER) entfernt → $_SERVER-Variablen direkt als Globals gesetzt
  • common.php: referers-Tracking-Block entfernt (Legacy-Statistik, kein Leser)
  • common.php: $beta entfernt (nur special/sacrificealtar.php genutzt, wird neu gebaut)
  • require/Chat.php: "Aktualisieren"-Button unterhalb der Nachrichten eingefügt (ruft chatPoll() auf);
  • feedback.php: aus _legacy/root/ ins Root verschoben; in publicPages/navExempt/noRestore eingetragen
  • ajax/ Ordner: alle *_ajax.php ins Unterverzeichnis verschoben;
  • lib/ bereinigt: weather, gilden, farbverwaltung, zunft_func_inc, navarray_lib, popup_texte_arr_inc,
  • core/user_func_inc.php → _legacy/core/ (creat_acc/create_char/change_stats etc. — keine Aufrufer)
  • require/RassenKlassenMatrix.php → _legacy/lib/ (keine Aufrufer)
  • common.php: tote lib/-requires entfernt; nur noch charstats_func_inc, output_func_inc, nav_func_inc
  • includes/ → _legacy/includes/ (markt-*.inc.php, alle ISO-8859-1, kein aktiver Aufrufer)
  • require/captcha_image.class.php + captcha_image.php + captcha_words.php + crypt.class.php → _legacy/lib/ (PHP 7 only, nur in altem petition.php-Code)
  • common.php: alle require_once zentralisiert — Weather, Item, Creature, Chat, Milestone, Quest,
  • training_basic.php / training_skill.php / training_master.php nach _legacy/root/ verschoben —
  • common.php: lib/user_lib_inc.php + lib/faehigkeiten_lib_inc.php-Includes entfernt; beide Dateien nach _legacy/lib/ verschoben (faehigkeiten nur Legacy-Aufrufer; $titles nur in dragon.php legacy-Code genutzt)
  • require/Chat.php: Komplettes Redesign auf "Burgplatz-Variante A" (Sprechblasen-Layout):
  • templates/edahnien.css: Altes Chat-CSS (.chat-widget/.chat-msg etc.) durch neues ersetzt;
  • templates/layout/info.php: Google-Fonts-URL um 'IM Fell English:ital@0;1' erweitert (für Blasen-Text)
  • require/Chat.php: Bubble-Text-Fix — backdrop-filter aus .bubble entfernt (verursachte unsichtbaren Text);
  • require/Chat.php: Charakter-Portraits in Avataren — getMessages() + post() per LEFT JOIN characters
  • templates/edahnien.css: .bubble backdrop-filter entfernt, background/color für alle Bubble-Varianten gesetzt
  • patches/current/check_schema.php: temporäre Debug-Datei gelöscht
  • addcommentary()/viewcommentary()-Aufrufe aus allen aktiven Dateien entfernt:
  • seed_equipment.php: $slotTab aufgeteilt — jeder Rüstungsslot hat eigenen Tab
  • configuration.php → _legacy/root/ (ISO-8859-1, page_header/showform, LoGD-Netz/PayPal-Settings — kein aktiver Aufrufer)
  • help.php → _legacy/root/ (popup_header/output, ISO-8859-1, Farben/FAQ/RP-Texte — kein aktiver Aufrufer)
  • CSS-Split: edahnien.css (258KB, 6813 Zeilen) → templates/css/*.css (12 Dateien);
  • DB-Fix: demo_hose/demo_tshirt/demo_flipflops → shop_tab='klamotten' (waren fälschlich in Slot-Tabs gelandet); Helme-Label HELME→Helme
  • dlg-system.js: Shop-Preis als einzelner .sw-price-num-Span (kein Text-Node-Split mehr)
  • edahnien.css: .sw-price + white-space:nowrap + flex-shrink:0; .sw-price-num neu
  • adminmodule/ NEU: 6 Admin-Subseiten als Module organisiert
  • lib/wald.php → _legacy/lib/ (ISO-8859-1, addnav/output/donationconfig Legacy);
  • reich.php / bestiary.php / halloffame.php → _legacy/root/;
  • quicklogout.php → _legacy/root/ (4 Zeilen, nur $quicklogout=true + common.php-Require, kein aktiver Aufrufer)
  • motd.php → _legacy/root/ (Legacy MOTD/Poll-System, popup_header, unserialize, accounts-Tabelle — kein aktiver Aufrufer)
  • petition.php → _legacy/root/ (Legacy FAQ/Regeln/Petition, popup_header, ISO-8859-1 — kein aktiver Aufrufer)
  • housemodules/gaestezimmer.php + keller.php + waffenkammer.php → _legacy/housemodules/

Warum

  • DK-Boni durften durch Admin-Stat-Edits nicht verloren gehen → Runtime-Berechnung
  • Kreatur-Skalierung war unintuiv mit additivem Delta → proportional verständlicher
  • Horizontales Scrollen der Nav auf Mobile war Platz-Verschwendung → Drawer spart Hauptbreite
  • battle.php war LoGD-Original-Code mit ISO-Encoding, Oktal-Bug und unserialize() → ersetzt
  • common.php Bereinigung reduziert Cognitive Load und entfernt Sicherheitsrisiken (unserialize, fsockopen)

[0.0.9] – 2026-05-18

Was

  • Chat-System: chat_rooms + chat_messages Tabellen (patch.sql) — Slug-basierte Räume, access ENUM(public/location/admin) + location_slug, type ENUM(normal/emote/scene/npc) + npc_name Spalten, bis 200 Nachrichten je Raum
  • Chat-System: require/Chat.php — Chat::render(slug, $char, opts), checkAccess(), post() mit Command-Parsing (/me /rpcmd /npc), buildMsgHtml() mit TextRenderer::chat() + Typen-Styling; Shared JS: chatInit/Poll/Send/Append + chatInsertCode + chatToggleMe + chatToggleCmd + chatUpdatePreview + chatParseBacktick (Client-Renderer); mehrere Widgets pro Seite möglich
  • Chat-System: chat_ajax.php — op=poll + op=post; 8 Sicherheitsschichten; getOrCreateRoom() im AJAX deaktiviert
  • Chat-System: CSS in edahnien.css — Toolbar (.chat-toolbar, .ct-c/b/me/admin), Live-Vorschau (.chat-preview), Nachrichtentypen (.is-emote/.is-scene/.is-npc), .village-chat Wrapper
  • village.php: Burgplatz-Chat eingebettet (nur $city === 'edahnien') — Chat::render('edahnien', $character, height:260); Raum hat access='location' → nur Spieler in Edahnien
  • Chat.php JS-Fix: chatSend nutzt r.json() statt r.ok?r.json():null — Fehlermeldungen vom Server werden jetzt angezeigt statt still verworfen
  • Chat.php Whitelist-Fix: Router::allow() → Router::allowPrefix() — allow() speichert den nackten Dateinamen; AJAX-Requests haben aber Query-Parameter (?ajax=1&op=post&...) → isAllowed() stripped Pfad aber nicht Query, exakter Key-Match schlägt fehl; allowPrefix() nutzt str_starts_with() und matcht korrekt

Was (Fortsetzung)

  • Combat-System Phase 1: require/CombatSession.php — DB-backed Session (combat_sessions), AP-Pool je kunstStufe (4/5/6/8), start/load/clear/save, updateEnemy/addStatusEffect/tickStatusEffects/allEnemiesDead
  • Combat-System Phase 1: core/CombatService.php — runRound() (attack_a1–a4 + flee), rollDamage (crit 1/20 = ×3), resolveEnemyAttack, styleModifier (combat_style_modifiers), applyWin (EXP/Gold/Loot + Buff::tick), rollLoot (creature_loot_entries), giftig-Trait (30% → poison DoT), DoT-Ticken am Rundenbeginn
  • Combat-System Phase 1: combat_ajax.php — AJAX-Endpunkt; op=state (lesend, kein CSRF), op=attack/auto_round/flee mit CSRF; turns-Check; Router::allowPrefix nicht nötig (eigene Datei)
  • Combat-System Phase 1: combat.php — aktiviert: CombatSession::start(), CSRF-Token, COMBAT_STATE phase=2+ready=true+csrf+ap; doppelter Ability-Block entfernt; Flüchten-Button jetzt AJAX (combatSendRound); vollständiger Battle-Loop in $script (sendRound/applyResult/HP-Balken/Log/AutoRound/EndScreen)
  • Combat-System Phase 1: patch.sql — CREATE TABLE combat_sessions + combat_style_modifiers + 8×8 Matrix-Seed

Was (Fortsetzung 2)

  • Character.php: effectiveAttack() / effectiveDefence() / effectiveInitiative() — addieren Rassen-Bonus (Race::bonusAtk/Def/Initiative) + Buff-Boni (Buff::statBonus) auf den DB-Basiswert; max(1) bei ATK/INI, max(0) bei DEF; Equipment-Boni als TODO für Phase 2
  • CombatService.php: rollDamage nutzt effectiveAttack(), resolveEnemyAttack nutzt effectiveDefence(), Initiative-Roll nutzt effectiveInitiative() — Rassen/Buff-Boni fließen ab sofort in alle Kampfberechnungen ein

Was (Fortsetzung 3)

  • Character.php: Equipment-Boni via loadEquipmentBonuses() — liest items JOIN item_definitions WHERE equipped_slot IS NOT NULL + stats_json (JSON mit att/def/ini/lp/maxlp/ap); $equipBonusCache für Lazy-Loading; effectiveAttack/Defence/Initiative berücksichtigen jetzt auch Equipment
  • Character.php: statBreakdown(stat) — liefert {base, race, equipment, buffs, total} für UI-Aufschlüsselung
  • Character.php: changeStatField() Indizes neu dokumentiert: 0=Angriff, 1=Verteidigung, 2=Initiative, 3=LP, 4=MaxLP, 5=Ausdauer(AP-Bonus)
  • Item.php: Inventar-Popup Kampfwerte-Tab zeigt effectiveAttack/Defence/Initiative statt Rohwerte + Breakdown im Profil-Tab (Basis/Rasse/Ausrüstung/Buffs je Stat)
  • Item.php: Equip-System schreibt bonus_atk/def/ini/lp/maxlp/ap in items-Tabelle (statt Pipe-Format)
  • edahnien.css: Combat-Log Scroll-Fix — #combat-log-panel als flex+overflow:hidden, .combat-log-box flex:1 min-height:0 → Log scrollt statt nach unten zu expandieren
  • edahnien.css: Stat-Breakdown CSS für .iv-sb-list/.iv-sb-group/.iv-sb-rows/.iv-sb-row (Inventar-Popup Profil-Tab)
  • edahnien.css + combat.php: Admin-Debug-Panel unter dem Gegner (nur Superuser ≥ 1) — zeigt SPAWN-Params (Difficulty/Level/Delta/DragonKills), KREATUR-STATS (Basis→Float→Gerundet für HP/ATK/DEF), SPIELER EFFECTIVE STATS (Aufschlüsselung ATK/DEF/INI mit allen Quellen); CSS: .combat-debug-panel + .cdbg-* (terminal-style, amber/jade Farbgebung, collapsible)
  • Creature.php: scaleToLevel() + spawn() füllen _debug-Array für das Admin-Panel

Was (Fortsetzung 4)

  • edahnien.css: Mobile-Responsive-Block angehängt (~280 Zeilen, alle 5 Seitentypen) — 3 Breakpoints: 768px, 480px, Landscape (max-height:500px + orientation:landscape)

Warum

  • Chat: Spieler sollen in Echtzeit miteinander kommunizieren können (global, ortsgebunden, kneipe etc.)
  • Mehrere Instanzen: verschiedene Raum-Slugs ermöglichen globalen Chat + Orts-Chat auf derselben Seite
  • Einbettbar: Chat::render('slug') genügt — kein Boilerplate nötig
  • Combat: Kampfsystem Phase 1 — vollständige Runden-Engine mit AP, DoT, Flucht, Loot, Sieg/Niederlage
  • Effective Stats: att/def = permanente Basiswerte in DB; was tatsächlich im Kampf zählt ist Basis + Rasse + Equipment + Buffs — saubere Trennung ermöglicht z.B. „Buff erhöht ATK um 5" ohne den DB-Wert zu verbiegen
  • Admin Debug: Kreatur-Skalierung ist komplex (Level-Delta × Wachstumsfaktor); Debug-Panel macht Berechnungen transparent für Balancing

[0.0.8] – 2026-05-18

Was

  • Kreatur-Portraits: templates/assets/creatures/ (10 Bilder) — portrait_path per SQL UPDATE in creature_definitions gesetzt
  • Combat-Layout: Dock schmaler (left/right: 60px), höher positioniert (bottom: 30px), Panels kürzer (220px)
  • CSS Cache-Busting: filemtime()-Versionierung in allen Templates
  • Nav: "Kämpfen" → "Automatisch kämpfen", "Abbrechen" → "Flüchten"
  • Shop-Widget im Dialog-System (dlg-system.js + edahnien.css + shop_ajax.php + markt.php)
  • Bugfix: dlgAjaxUrl() fügt jetzt &ajax=1 ein → Router::clear() wird nicht mehr durch Shop-Calls getriggert
  • Rucksack: Profil-Tab (3. Tab mit Charakter-Portrait, Kampfwerten, Ressourcen)
  • Rucksack: Tab-Buttons per CSS-Klasse (.iv-tab / .iv-tab-active) statt Inline-Style
  • Rucksack: Quest-Tab heißt jetzt "Auftragsbuch"
  • common.php: navigation-Klasse auskommentiert, GAME_VERSION aktualisiert
  • admin_patch.php: Auto-Bild-Erkennung + Versionsnummer-Update
  • Visuelle Einstellungen: weather_fx + time_fx per character.prefs['display'] (display_settings_ajax.php)
  • Profil-Popup: Settings-Toggle Wetter-/Tageszeit-Effekte (profil_popup.php, game_topbar.php)
  • Output.php: enableWeatherEffects()/enableTimeEffects() prüfen Prefs vor dem Setzen der Flags
  • Fix: dsToggle() entfernt sofort gt-* Body-Klassen bei Deaktivieren (kein Reload nötig)
  • Fix: Tageszeit-Hintergrundbild-Filter (inline <style>) wird bei Toggle sofort via JS überschrieben
  • Fix: info.php prüft time_fx/weather_fx Prefs serverseitig vor backgroundFilter()-Aufruf
  • Rucksack: Equip-System — Items ausrüsten/ausziehen (equip_ajax.php + Item.php + edahnien.css)
  • Rucksack: Paper-Doll-Slots mit ID-Attributen für JS-Updates
  • Rucksack: Item-Stats (ATK/DEF/LP) aus stats_json im Detail-Panel anzeigen
  • Rucksack: Ausrüstungs-Kategorie "Ausrüstung" in Sidebar-Filter
  • Rucksack: Paper-Doll-Slots größer (56×44 statt 48×36, Waffe/Schild 108×36 statt 90×30)
  • Rucksack: Avatar im Paper-Doll größer (100×148 statt 80×116)
  • Rucksack: Slot-Labels lesbarer (Glyph 17px, Label 9px, Höhe 50px)
  • Rucksack: Drag & Drop — Ausrüstungskarten auf Slot oder Avatar ziehen zum Ausrüsten
  • Auftragsbuch: 3-Spalten-Layout (Kategorien | Karten | Detail) — Quest::renderPopupTab() komplett neu
  • Auftragsbuch: Kategorien Chronik (story) / Legenden (legend) / Völker (faction) / Aufgaben (side) / Tagesaufgaben (daily)
  • Auftragsbuch: Kategorie-Filter + Status-Filter (Aktiv / Erledigt) in der Sidebar
  • Auftragsbuch: Karten mit Kategorie-Badge + Status-Punkt, Detail-Panel mit Objectives + Fortschritt + Belohnungen
  • quest_definitions.category ENUM um 'legend' + 'faction' erweitert
  • Rucksack: Tab "Profil" → "Einstellungen" — 2-Spalten-Layout (Sidebar + Inhalt)
  • Einstellungen: Kategorien Charakter / Biografie / Darstellung / Klang / Datenschutz
  • Einstellungen: Charakter — Avatar, Name, Titel, Badges, XP, Kampfwerte, Ressourcen
  • Einstellungen: Biografie — Freitext-Textarea (max. 2000 Zeichen), speichert in prefs['bio']
  • Einstellungen: Darstellung — Wettereffekte + Tageszeit-Atmosphäre (dsToggle, identisch zum Profil-Popup)
  • display_settings_ajax.php: op=bio speichert Biografie in characters.bio (eigene TEXT-Spalte, kein prefs-Aufblähen)
  • Character.php: bio() Getter + setBio() Setter (characters.bio, max. 5000 Zeichen)
  • Topbar: Inventar-Button entfernt, Profil-Button (⚙) heißt jetzt "Profil & Inventar" — öffnet Rucksack-Popup auf Gegenstände-Tab
  • Topbar: Profil & Einstellungen-Popup wird nicht mehr über Header-Button geöffnet (dsToggle bleibt über profil_popup.php verfügbar)
  • Fix: popup-inventar div in scene.php ergänzt (Profil & Inventar-Button auf Scene-Seiten funktioniert jetzt)
  • Fix: Drag & Drop — ivSelect() im dragstart-Handler → Stat-Vorschau beim Ziehen eines Items
  • Admin: Entwicklungsstand-Dashboard (admin_devstatus.php) — Große + Kleine Module, Fortschrittsbalken, Checkliste, Add/Delete per AJAX, Datendatei patches/devstatus.json
  • forest.php: rudimentäres Waldmenü — Waldhütte, Plumsklo (Platzhalter), In den Wald vordringen (→ combat2.html), Ins Dickicht (gesperrt bis Phase 2)
  • combat.html: "Abbrechen"-Link im Battle-Topbar + "Zurück zum Wald" auf Sieg/Niederlage-Screen (→ forest.php)
  • combat2.html: Vollbild-Kampftemplate (Jade-Skin) — SVG-Waldkulisse, schwebende Panels, Portrait-Animationen (Atmen/Float/Blinzeln), Schadens-Floats, vollständige JS-State-Machine (Angriff/Fähigkeiten/Items/Flucht), Sieg/Niederlage-Screens mit Rückkehr zu forest.php ✅ FERTIG
  • combat2.html: Standard game_topbar (info-topbar), linke Battle-Nav (1/5/∞ Runden, Flüchten mit INT-W20-Mechanik), 3-Spalten Bottom-Dock (30/50/20%), Kampfanimationen (Slash-Sweep, Ring-Burst, Portrait-Lunge, BG-Flash), Sub-Bar-Fix Fähigkeiten, Admin-Links Waldrand/Dorf
  • Kreatur-System Phase 1: patch.sql (creature_definitions, creature_spawn_locations, creature_loot_tables, creature_loot_entries), require/Creature.php (spawn/scaleToPlayer/Traits/Flucht/Zähmen/Loot/Resistenzen/Admin/Locations), admin_creatures.php (7-Tab-CRUD-Editor: Grunddaten/Stats/Verhalten/Traits/Loot/Spawn-Orte/Zähmen), docs/seed_creatures.php (8 Waldrand + 5 Lichtung + 2 Bosse + 5 Loot-Tabellen mit Einträgen)
  • docs/creature_system.md: Kreatur-System Designdokument (vollständig) — DB-Schema (creature_definitions mit Traits/Fluchtinstinkt/Zähmbarkeit/INT/buff_resistance/game_phase/element/size, creature_spawn_locations je Ort einzeln schaltbar, creature_loot_tables, creature_loot_entries), vollständige Trait-Bibliothek (25 Traits in 5 Kategorien), Flucht- und Zähm-Mechanik mit INT-Würfelwurf, Creature.php Interface, Stat-Scaling-Formel, Game Design (7 Locations, Kreatur-Roster Phase 1 mit 13 Kreaturen + 3 Bossen, Loot-Tabellen, Stil-Matrix), Admin-Editor 8-Tab-Spec + Preview-Kampf, Implementierungs-Reihenfolge Phase 1–4
  • Arachnophobie-Modus: Trait 'spinne' in Creature.php + getSafeDisplay() (gibt _portrait_html CSS-Blob zurück statt Bildpfad) + isSpider(); wabbelnder CSS-Blob (.arachno-ph / .arachno-ph__blob) in edahnien.css mit ×-Augen, ∿-Mund und arachno-wobble-Animation; Einstellungs-Toggle in Item.php (Darstellung-Tab); display_settings_ajax.php erlaubt 'arachnophobia_mode'; waldspinne + spinnenkoenigin erhalten Trait 'spinne' in seed_creatures.php
  • Combat-Template: templates/layout/combat.php (Vollbild-Jade-Skin) — combat-page body-Klasse, game_topbar.php + battle-nav ($nav) + combat-frame ($content), Intro-Screen (position:fixed, combat-screen.active) mit combat_forest_bg.php + Intro-Animationen, End-Screen (position:fixed) mit Sieg/Niederlage-Platzhalter, page-veil JS-Übergang, combatSkipIntro()-Skeleton; Variablen: $sceneLabel, $backUrl, $combatSprites
  • templates/layout/partials/combat_forest_bg.php: Waldkulisse als Partial — nutzt templates/assets/poi/wald_normal.png als echtes Hintergrundbild (combat-bg__img, object-fit:cover, brightness-Filter), leichtes SVG-Overlay für Vignette + grüne Lichtstrahlen + Glühwürmchen; $__cbgBg überschreibbar für andere Kampforte (z.B. Höhle)
  • core/Output.php: $sceneLabel, $backUrl, $combatSprites zur global-Deklaration in render() hinzugefügt
  • combat.php: Neue Spielseite — spawnt Kreatur via Creature::spawn('forest_waldrand'), rendert Panels/Portraits/Dock PHP-seitig, setzt COMBAT_STATE JSON in $headscript, battle-nav (dimmed Phase-1), Flüchten-Button (→ forest.php) aktiv; Fallback-Kreatur wenn DB leer; Arachnophobie-Filter via getSafeDisplay()
  • forest.php: "In den Wald vordringen" → combat.php; neuer Link "In den Wald vordringen (DEMO)" → docs/template/combat2.html
  • Kampfkunst-Progression: characters.kunst_stufe (1–4) + kunst_exp_invested + meisterkampf_stufe + meisterkampf_cooldown Spalten; class_abilities Tabelle (32 Einträge: 8 Klassen × 4 Angriffe a1–a4, AP-Kosten 0/2/4/7, Schadensmod ×1.0–×2.7, unlock per kunst_stufe 1–4); combat.php lädt Angriffe aus DB, zeigt im Dock 4 Slots (freigeschaltet: Name + AP + ×Mod, gesperrt: Schloss + KK-Stufe X); COMBAT_STATE.player.abilities + kunst_stufe + class_id; Neues Attack-Grid CSS (combat-attack-grid, ca-attack, ca-locked, ca-ap, ca-mult, combat-action-row2)
  • Kampfkunst-Training Routing: training.php op=skill → leitet Magier-Klassen (6/7/8) zu tempel.php?op=skill weiter; tempel.php hat neuen op=skill Block (Avara für Klassen 6+8, Sevra für Klasse 7, gleiche EXP-Kosten wie training.php); tempel.php whitelisted village.php-Prefix
  • Avara-Dialog: neuer Branch "Meine Kampfkunst vertiefen" (klasse_in:[6,7,8]) → magie_kunst_intro → avara_kunst_bereit/sevra_kunst_bereit → navigate tempel.php?op=skill; "Magie trainieren" als separater Branch (vorher war alles in magie_intro); Seed: patches/current/seed_dialog_avara.php (via admin_patch.php einspielen)
  • Design-Doc aktualisiert: docs/ausbildungslager_design.md Teil 4 Meisterkampf neu geschrieben — Level-Up-Mechanismus (EXP Level²×100 Schwellwert, Kampf vs Krom, kein Cooldown auf Niederlage, sofort nochmal), Krom-Skalierung, Trainingsort-Zuständigkeit Tabelle (Martial 1–5 bei Krom, Magier 6–8 bei Tempel)
  • Design-Doc aktualisiert: docs/combat_system.md — Kampfkunst-Angriffe (class_abilities) vollständig dokumentiert: alle 32 Angriffe (8 Klassen × 4 Slots), Stufennamen (Lehrling/Geselle/Meister/Großmeister + Schüler/Adept/Meister/Erzmagier), AP-Pool je Stufe (4/5/6/8), EXP-Kosten, Trainingsort-Zuständigkeit, Angriffe vs. Fähigkeiten Unterschied, Meisterkampf-Abgrenzung, Implementierungsstand

Warum

  • Shop-Widget: Händler auf dem Markt sollen Items verkaufen (Dialog-System eingebettet)
  • Profil-Tab: Design-Update aus Rucksack D.html — Questbuch + Profil als neue Tabs
  • Bugfix: Class "navigation" not found in common.php nach _legacy/ Migration
  • Visuelle Einstellungen: Spieler können Wetter-/Tageszeit-Atmosphäre deaktivieren (Accessibility/Performance)
  • Bugfix: Tageszeit-Effekt blieb nach Toggle an weil Body-Klasse nicht sofort entfernt wurde (inline <style> in <head> ignorierte Body-Klassen-Entfernung)
  • Equip-System: Gekaufte Ausrüstungsgegenstände (Flip Flops, Hargummi etc.) sollen ausrüstbar sein
  • Auftragsbuch: Übersichtliches 3-Spalten-Layout + Kategorien für Hauptquests, Persönlichkeits-Quests, Ortsquests, Nebenquests, Tagesquests
  • Einstellungen: Konsolidiert Profil + Settings in einem Tab mit einheitlichem Layout

[0.0.7e] – 2026-05-17

Was

Warum

[0.0.7d] – 2026-05-17

Was

  • lib/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)

Warum

[0.0.7c] – 2026-05-17

Was

  • Meilenstein-Overlay: hidden-Attribut direkt im HTML gesetzt (info.php + interactive.php)
  • dlg-system.js + admin_milestones.php: showMilestone() entfernt hidden, closeMilestone() setzt es zurück
  • Item::getAll() + Output::render(): try-catch ergänzt — fehlende DB-Tabellen brechen das Inventar-Popup nicht mehr

Warum

[0.0.7b] – 2026-05-17

Was

  • characters-Tabelle: 43 Legacy-Spalten gelöscht (Row-Size-Fix + Cleanup)
  • .htaccess neu: PHP-Upload-Limits 32M/64M (mod_php + mod_php8)
  • admin_patch.php: Upload-Fehlermeldung verbessert, Formular zeigt aktives Limit
  • require/Character.php: Getter für tote Spalten entfernt (familyname, beruf, labyrinthTurns, blut)
  • core/user_func_inc.php: Tote editierbare Felder aus $char_extra_infos entfernt
  • dragon.php: Tote Einträge aus der nochange-Liste entfernt
  • docs/migration_characters.sql: CREATE TABLE auf bereinigtes Schema aktualisiert

Warum

Row-Size-Fehler (#1118)

Gelöschte Spalten nach Kategorie

[0.0.7] – 2026-05-16

Was

Milestone-System + Live-Update nach Kauf

Burgviertel + Dialog-Geschwindigkeit Bugfixes

Admin-Charakter-Editor (admin_charedi.php)

Milestone-Definitionen DB-Migration + Admin-Seite

Bugfix: Inventar-Popup veraltet nach AJAX-Item-Erhalt

Bugfix: admin_charedi.php — Eigentumsurkunde löschen räumt Haus auf

Milestone: News-Eintrag bei Freischaltung

Milestone artwork_path — dynamisches Hintergrundbild

Bugfix: admin_milestones.php — Vorschau-Button JS-Fehler

Bugfix: Buff.php graceful degradation

Burgschmiede — Rohstoffverkauf

Legacy-Bereinigung

Hausbau — Bauzeit auf Spielzeit umgestellt

DB-Bereinigung — Legacy-Tabellen umbenennen

Kinder-System entfernt

login.php — Legacy-Cleanup

Multi-NPC-Dialog + Magie-Training im Tempel

Tempel der Stille — Avara NPC-Dialog (Phase 1)

Training — Aktionen freigeschaltet

Quest-System (technisches Fundament)

Bugfix: patch.sql — DELIMITER + RENAME TABLE nicht PDO-kompatibel

Bugfix: Tageszeit-Atmosphäre — Mittag/Abend sahen aus wie Nacht

markt.php — aus _legacy wiederhergestellt

Wetter + Tageszeit-Effekte: Opt-in statt Global

Rathaus — Mira-Dialog + Sidebar-Navigation

Das Rathaus — Vollausbau

Bugfix: Newday-Countdown stimmt nicht mit Tagesphase überein

Warum

  • Spieler konnten Dialog-Geschwindigkeit nicht persistent setzen (fetch scheiterte wegen fehlender whitelist)
  • Ohne Nav-Link war Zimmererei nur über Dialog erreichbar
  • NPC-Dialog muss Bautrupp-Verweis immer zeigen, egal ob Spieler schon Haus hat
  • Milestone-Definitionen in PHP-Konstante waren unhandlich — DB erlaubt einfache Anpassung ohne Code-Deploy

[0.0.6] – 2026-05-15

dialog_siegbert_burgviertel.json — Bautrupp-Link ohne Haus

Was

  • Neue Choice im intro-Node: "Ich möchte beim Bautrupp vorbeischauen." (nur bei haus_keines)
  • Neuer Node bautrupp_schauen: Siegbert erklärt kurz dass Hannes nebenan steht und
  • goto_bautrupp navigiert wie gehabt zu bautrupp.php

Warum

  • Ohne Haus konnte man Hannes nur über bautrupp.php direkt erreichen, nicht über Siegbert
  • Hannes hat den haus_keines-Fall schon drin (sagt "kein Grundstück — kein Haus, geh zu Siegbert")
  • Seed wird via seed.php automatisch eingespielt (liest docs/dialog_siegbert_burgviertel.json)

admin_patch.php — Seeds einzeln einspielen

Was

  • Neuer Handler op=run_seed: listet alle seed*.php in patches/current/ auf
  • Nach Ausführung: Erfolg/Fehler-Banner + Seed-Output in <pre>
  • Übersicht: neuer „Seeds einspielen"-Button neben „Datenbank aktualisieren"
  • Nav: neuer Link „Seeds einspielen"
  • Sicherheit: Dateiname wird gegen /^seed[\w\-]*\.php$/ validiert, kein Pfad-Traversal

Warum

  • op=run_sql führt immer den vollen Patch aus (inkl. DROP TABLE) — zu destruktiv für
  • Einzelne Seeds (z.B. seed_burgviertel.php) brauchen einen sicheren Aufruf ohne

bautrupp.php — Materiallager entfernt

Was

  • Shop-Handler op=shop_kaufen entfernt
  • $shopToken entfernt
  • Flash-Message-Block (bautrupp_msg) entfernt
  • Materiallager-HTML-Block (Hannes' Vorrat) entfernt
  • Fehlermeldungen "Kauft sie beim Bautrupp" → "findet sie im Wald oder am Markt"

Warum

  • Das Materiallager war eine Überbrückungslösung für Tests
  • Baustoffe sollen langfristig aus dem Wald kommen (Loot) oder am Markt gekauft werden
  • Möbel kommen später über einen eigenen Händler

DB-Fehlerlog — DbErrorLogger + admin_grotte Tool

Was

  • require/DbErrorLogger.php (neu): file-basierter Logger für DB-Fehler
  • require/Database.php:
  • admin_grotte.php:
  • logs/.htaccess (neu): Require all denied — kein direkter Web-Zugriff auf Logs

Warum

  • DB-Fehler waren bisher nur in $db->lastError() sichtbar — flüchtig, nur der letzte Fehler
  • Beim Schema-Debug (owner → owner_id) musste burgviertel.php temporär den echten Fehler ausgeben
  • Mit dem Log sieht man alle DB-Fehler persistent, sortiert, mit SQL und aufrufender Datei

patch.sql — Item-Tabellen DROP + CREATE (Schema-Reset)

Was

  • patch.sql: DROP TABLE IF EXISTS items, item_definitions, itemtransfer

Warum

  • Altes LoGD-items hatte Schema (id, name, class, value1, owner, gold, gems, …) — kein definition_id
  • Item.php JOIN auf d.id = i.definition_id schlug fehl: "Unknown column 'i.definition_id' in 'on clause'"
  • Gleiche CREATE TABLE IF NOT EXISTS-Falle wie bei houses — DROP garantiert sauberes Schema

patch.sql — Haussystem-Tabellen DROP + CREATE (Schema-Reset)

Was

  • patch.sql: DROP TABLE IF EXISTS für house_keys, house_furniture, house_rooms,
  • ALTER TABLE Migration (owner → owner_id) entfernt — wird nicht mehr gebraucht

Warum

  • Alte houses-Tabelle hatte owner-Spalte statt owner_id → INSERT schlug mit
  • CREATE TABLE IF NOT EXISTS überspringt stillschweigend falls Tabelle existiert → Schema-Mismatch

common.php — AJAX-Requests leeren Whitelist nicht mehr

Was

  • common.php: Router::clear() wird bei AJAX-Requests (?ajax=1) NICHT mehr aufgerufen
  • Bisher wurden nach jedem AJAX-Call die Whitelist-Einträge geleert → nachfolgende

Warum

  • Beim Dialog-AJAX-Kauf lief der Flow: burgviertel.php laden (Whitelist gesetzt) →
  • AJAX-Calls haben eigene CSRF-Sicherheit (DialogAjax::verify()) und brauchen kein

patches/current/patch.sql — TIMESTAMP NULL DEFAULT NULL

Was

  • items-Tabelle: expires_at TIMESTAMP DEFAULT NULLexpires_at TIMESTAMP NULL DEFAULT NULL
  • MariaDB strict mode: TIMESTAMP braucht explizites NULL in der Spalten-Definition

admin_patch.php — Robuster SQL-Splitter + UI-Verbesserungen

Was

  • splitSqlStatements(): neuer Zeichenparser ersetzt explode(';', $sql)
  • runSqlStatements(): neues Output-Format
  • CSS: .patch-ok-group, .patch-ok-summary, .patch-ok-list für eingeklappte OK-Gruppe

admin_patch.php — lastError() Methode statt privater Property

Was

  • admin_patch.php: $db->lastError$db->lastError() (öffentliche Methode)
  • Database::$lastError ist private — direkter Zugriff warf Fatal Error (Throwable)

burgviertel.php — Kaufabbruch-Fix + Dialog-Verbesserungen

Was

  • burgviertel.php: House::create() in try-catch — RuntimeException führte zu HTML-Output
  • burgviertel.php: Gold-Abzug erst NACH erfolgreichem House::create() (atomische Reihenfolge)
  • docs/dialog_siegbert_burgviertel.json: spoken: "Bezahlt." an der Bezahlen-Choice
  • templates/partials/dlg-system.js:

Warum

  • House::create() konnte eine RuntimeException werfen wenn INSERT in DB fehlschlug
  • Spieler sahen nach "Bezahlen" klicken: Spieler-Bubble + dann nichts. Jetzt sehen sie

Dialog-System v1.4 — Speed-Toggle-Button

Was

  • dialog_speed.php (neu): POST-Endpoint speichert prefs['dialog_speed'] (1/2/3) in DB
  • require/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-Icon

Warum

  • Spieler, die Dialoge wiederholt lesen, sollen durch Klick auf den Speed-Button
  • Einstellung wird im Charakter-Prefs gespeichert und gilt global für alle Dialoge

Item-System Phase 1 — item_definitions + items + Inventar-Popup + Haus-Verdrahtung

Was

  • require/Item.php (neu): Statische Inventory-Klasse
  • core/Output.php: $inventoryHtml Static + getter/setter
  • templates/layout/partials/game_topbar.php: 🎒 Rucksack-Button (vor Post)
  • templates/layout/info.php: Inventar-Popup (#popup-inventar) eingefügt
  • burgviertel.php: Eigentumsurkunde-Item geben nach Grundstückskauf
  • bautrupp.php:
  • house_ajax.php: neuer op=moebel_einbauen
  • housemodules/_gold_ui.php: Zeigt "Möbel einbauen"-Form wenn kein Mobiliar + Item im Inventar
  • patch.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 Stubs
  • dialog.php: give_item + take_item Effect-Handler implementiert
  • patches/current/seed_burgviertel.php (neu): Bugfix für kaputte Dialog-JSONs als standalone Browser-Seed
  • patches/current/seed.php: Siegbert/burgviertel + Hannes/bautrupp Bugfix in seed.php integriert
  • patches/current/patch.sql: avatar_path für Siegbert + Hannes auf characters/npcs/*_portrait.png gesetzt (war leer) + sofortige UPDATE-Statements für bereits deployed DBs
  • templates/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).
  • Bugfix Badnav bei Dialog-Navigation: 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.php
  • burgviertel.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 angepasst
  • patches/current/patch.sql: INSERT für house_locations: burgviertel_edahnien direkt in patch.sql — reicht nun install.php statt separater Seed-Ausführung
  • docs/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ückskauf
  • newday.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 entfernt
  • burgviertel.php: $grundsteuerPreis + grundsteuer_preis extraVar entfernt
  • patches/current/seed_haussystem.php + seed.php: grundsteuer_preis = 0 (Spalte bleibt im Schema, wird nicht verwendet)

Architektur-Entscheidungen

  • Keine Crafting/Quest-Tabellen in Phase 1 (folgen wenn nötig)
  • item_definitions.slug UNIQUE KEY für stabilen Lookup ohne ID-Hardcoding
  • Stackable items: UPDATE statt INSERT on conflict (kein ON DUPLICATE KEY nötig dank explizitem Stack-Check)
  • Inventar-Popup: Server-Side-Rendering via $inventoryHtml Global-Variable, kein AJAX-Reload
  • Shop-CSRF: eigener Session-Token bautrupp_shop (kein Dialog-Token — Shop ist kein Dialog-Action)

Haussystem: Schritte 6–8 — Gold-System, Raum-Module, Schlüssel

Was

  • house_ajax.php (neu): POST-Handler für alle Haus-Aktionen
  • houses.php: $houseToken + houseFlashHtml() + Flash vor Modul-Output
  • housemodules/_gold_ui.php (neu): Gemeinsames Gold/Gems-UI für alle Gold-Räume
  • housemodules/stube.php (vollständig):
  • housemodules/privatgemach.php (vollständig): Gold-UI
  • housemodules/schatzkammer.php (neu): Gold-UI (Besitzer + Schlüsselinhaber)
  • housemodules/kueche.php (neu): Komfort-Info
  • housemodules/arbeitszimmer.php (neu): +5% EXP Info
  • housemodules/waffenkammer.php (neu): Item-Lager Stub
  • housemodules/keller.php (neu): Extra-Lager Stub
  • housemodules/gaestezimmer.php (neu): Gast-Buff Stub
  • templates/edahnien.css: .btn-evo, .btn-evo--sm, .btn-evo--danger, .input-evo

Haussystem: Schritt 5 — Innenansicht

Was

  • houses.php (neu): Innenansicht + Raum-Dispatcher
  • housemodules/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-locked
  • patch.sql: content_strings für houses.php (15 Texte)

Haussystem: Schritt 4 — newday + setnewday Integration

Was

  • newday.php: Neuer Schritt 5c (nach Schlafen-Flags, vor save())
  • setnewday.php: Neue Funktion _nd_houses(), nach _nd_maintenance() aufgerufen

Haussystem: Schritt 3 — Burgviertel + Bautrupp

Was

  • burgviertel.php: Komplett neu — ersetzt alten Platzhalter
  • bautrupp.php (neu): NPC Hannes
  • docs/dialog_siegbert_burgviertel.json (neu): 11 Nodes, condition-basiert auf Haus-Flags
  • docs/dialog_hannes_bautrupp.json (neu): 25 Nodes, alle 4 AJAX-Operationen
  • require/DialogSystem.php: Neue Haus-System-Flags (hat_grundstueck, haus_keines, haus_plot,
  • patch.sql: NPC-Beschreibungen (siegbert, hannes) + Dialog-INSERTs + content_strings

Haussystem: Schritt 2 — Kern-Klassen

Was

  • require/House.php: Model für houses-Tabelle
  • require/HouseRoom.php: Model für house_rooms-Tabelle
  • require/HouseKey.php: Model für house_keys-Tabelle
  • patch.sql (Korrektur): houses.status ENUM ergänzt um 'plot' als erster/Default-Wert

Architektur-Entscheidungen

  • Grundstücks-Tracking über houses.status='plot' — kein prefs-Flag
  • Dragon Kill: kein Einfluss auf Haus-Gold (sicher)
  • Einbruch: Verlust je Möbelstück, %-Werte als Settings-Variablen
  • Gold technisch in house_furniture gespeichert, UI bündelt nach Raum
  • build_finish_date: DATE-Feld, Newday prüft ob Bau fertig
  • Baustoffe: Inventar-Check entfällt vorerst (bis neues Item-System aktiv)

Haussystem: Schritt 1 — DB-Schema + Seeds

Was

  • patch.sql: 5 neue Tabellen (house_locations, houses, house_rooms, house_furniture, house_keys)
  • patch.sql: characters um ausgeruht_stufe + haus_auslogort erweitert
  • patch.sql: alle Haussystem-Settings (Kosten, Limits, Buff-Werte)
  • seed_haussystem.php: house_locations (Burgviertel Edahnien)
  • Item-Definitionen (Baustoffe, Möbel, Urkunde) bewusst ausgelassen — folgen mit neuem Item-System
  • _legacy/houses/: altes Haussystem gesichert (6 PHP-Dateien + 20 housemodules)

Kutschenhof: NPC-Dialog + Reisesystem

Was

  • kutschenhof.php: Komplett neu — NPC-Dialog Minna statt statischer Liste
  • kutsche.php (neu): AJAX-Handler für op=reisen und op=eilreisen
  • dialog.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_costs
  • todos/todo_dialoge_integration.md: Dialog #19 (Minna/Kutschenhof) auf ✅ gesetzt

Dialog-Architektur

  • Per-Zielort-Nodes (reise_almhausenreise_ewigbluete) weil fetchAction
  • condition_override aus dem Spec-JSON nicht implementiert (nicht standard) —
  • angekommen-Node navigiert zu village.php (Character-Stadt wurde bereits geändert)

Dialog-Editor: Vorschau-Funktion

Was

  • dialog_editor.php: Neuer 👁 Vorschau-Button in der Toolbar
  • Bugfix: collectTree() liest jetzt Node-Key aus dem Eingabefeld (Umbenennen funktioniert)
  • Bugfix: addLine() focus nach appendChild korrigiert
  • Bugfix: $v($formJson) im <script>-Tag durch json_encode(..., JSON_HEX_TAG) ersetzt (Nodes wurden nicht geladen)
  • Bugfix: onclick-Handler (toggleCard etc.) auf window exportiert (waren im IIFE nicht erreichbar)

Dialog-Editor: Visueller Node-Editor

Was

  • dialog_editor.php: Komplettneuschreibung — rohe JSON-Textarea durch vollständigen visuellen Editor ersetzt

Bleibt unverändert

  • Auth-Check isSuperuser(4), Router, NPC-Slug-Query
  • Op: toggle, delete — identische Logik
  • Liste (Übersicht aller Dialoge) — identische Tabelle
  • POST-Handler (Felder npc_slug, context_slug, tree_json, active) — identisch

Warum

Dialog-AJAX: CSRF-Schutz + Validierungs-Helper

Was

  • require/DialogAjax.php (neu): Sicherheits-Klasse für alle Dialog-AJAX-Handler
  • templates/partials/dlg-system.js: dlgAjaxUrl() hängt dlg_token, dlg_npc, dlg_ctx an jeden AJAX-Call
  • inn.php, mara.php: DialogAjax::verify() in AJAX-Handlern ergänzt

Warum

Security-Logging + Cheater-Flagging

  • Jeder requireCondition()-Fehler schreibt einen Eintrag in security_log
  • Überschreitet ein Charakter security_bypass_threshold (default 5) Failures
  • Admin-Review-UI: TODO (später) — liest prefs['security']['flagged_for_review']
  • Schwellwerte per settings-Tabelle konfigurierbar ohne Code-Änderung

Dialog-System: Eingabefeld (node.input) — v1.2

Was

  • templates/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-error
  • docs/dialog_spec.md: neue Section §7 dokumentiert das input-Property vollständig (v1.2)

Warum

Dialog-Integration: Mara / Zimmer

Was

  • 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.php
  • patches/current/seed.php (neu): Seed für npc_descriptions (Mara) und dialog_trees (mara/zimmer)

Warum

Patcher: Bilder/Binärdateien im ZIP

Was

  • admin_patch.php: buildPatchZip() und buildSelfUpdateZip() verwenden jetzt addFromString() + file_get_contents() statt addFile() für alle Dateien inkl. CHANGELOG.md

Warum

Dialog-System: Charm- und Verheiratet-Conditions

Was

  • require/DialogSystem.php: zwei neue Conditions in choiceAllowed():
  • hasFlag() um System-Flag-Mechanismus erweitert: Flags die automatisch aus DB-Feldern abgeleitet werden ohne manuellen prefs-Eintrag

Warum

Versionsnummer-Korrektur

Was

  • common.php: GAME_VERSION-Konstante von 0.0.1 auf 0.0.5 aktualisiert
  • patch.sql: game_version in der settings-Tabelle auf 0.0.5 gesetzt (via INSERT ... ON DUPLICATE KEY UPDATE)

Warum

Bank: Dialog-Integration (Aldric · Das Schatzgewölbe)

Was

  • bank.php: Komplette Neuentwicklung auf Basis des Dialog-Systems
  • require/Character.php: neue Methoden setGoldInBank(), addGoldInBank(), setSchliessfachStufe(), Getter schliessfachStufe()
  • require/DialogAjax.php: System-Flag has_schliessfachschliessfachStufe() > 0
  • require/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)

Warum

Schließfach-System (Stufen)

  • Stufe 0 = kein Schließfach (has_schliessfach-Flag false)
  • Stufe 1–N = gemietet; Max-Stufe per bank_sf_max_stufe (default 3)
  • schliessfach.php (Inhalts-Verwaltung) ist Platzhalter — folgt in späterem Update

Sicherheits-Overhaul: inn.php + training.php

Was

  • inn.php: CSRF-Schutz für alle zustandsändernden Aktionen
  • training.php: CSRF-Schutz für alle POST-Formulare und GET-Aktionen

Warum

Patch-Deploy Robustheit

Was

  • templates/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äuft
  • templates/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)

Warum

Bank-Dialog: Doppeltext + stale Conditions Fix

Was

  • 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-Cache
  • sf_aufgeben_ja.loop_to"sf_aufgeben_tschuss" (neuer Node mit navigate: "bank.php") → Seiten-Reload erzwingt frische Conditions für intro

Warum

Charinfo-Sidebar: Gold + Edelsteine

Was

  • core/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 ausgeblendet

Warum

Dialog-Input: Optik überarbeitet (Input + Button inline)

Was

  • templates/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.

Warum

Schließfach: Edelstein-Tresor (Inline-Panel in bank.php)

Was

  • 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-msg
  • dialog_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 Seite
  • patch.sql: gemssafe INT UNSIGNED NOT NULL DEFAULT 0 + Setting bank_sf_gems_kapazitaet = 10

Warum

Schließfach-Widget im Dialog

Was

  • require/DialogSystem.php: neue Vars gems, gemsinsafe, sf_kapazitaet, sf_token in der vars-Map
  • templates/partials/dlg-system.js:
  • bank.php: altes sf-panel-HTML + JS-Block entfernt (sfAction(), sfShowMsg(), Panel-Markup) — ersetzt durch Dialog-Widget
  • templates/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-msg
  • dialog_trees (aldric/bank): Node sf_oeffnen als Widget-Node hinzugefügt; in sf_menu neue Choice "Ich will zum Schließfach." → sf_oeffnen

Warum

Fluchkessel: Agna erkennt Fluchstatus + Klagen-Easter-Egg

Was

  • fluchkessel.php: neuer AJAX op=fluch_klagen:
  • require/DialogSystem.php: System-Flag has_fluchBuff::get($character, 'fluch') !== null
  • templates/partials/dlg-system.js: fetchAction unterstützt jetzt res.navigate
  • dialog_trees (agna/fluchkessel):

Warum

Der Fluchkessel (Heiler · NPC Agna)

Was

  • fluchkessel.php (neu): Heilerin Agna als vollständiger Dialog-NPC
  • require/DialogSystem.php:
  • village.php: "Das Lazarett" / lazarett.php → "Der Fluchkessel" / fluchkessel.php
  • patches/current/seed_fluchkessel.php (neu): Seed für npc_descriptions (Agna), dialog_trees (agna/fluchkessel), settings (4 Kosten-Werte), content_strings
  • patches/current/patch.sql: 4 neue Settings (fluchkessel_*_kosten)

Dialog-Baum

  • intro → Hauptmenü (7 Optionen)
  • heilen → Kosten zeigen + Bestätigung → heilen_ja (Action) → heilen_done
  • fluch → Beschreibung + Bestätigung → fluch_ja (Action) → fluch_done
  • gift_check_ja/gift_keine_zutat → via has_item/has_item_not für Giftdrüse
  • traenke_menu → 4 Tränke mit je has_item/has_item_not Conditions
  • info → Lore → info_zutaten → Zutaten-Übersicht
  • gehen → navigate: village.php

has_item / has_item_not Conditions

  • has_item: gibt immer false zurück → Choice ausgeblendet (Spieler hat keine Zutaten)
  • has_item_not: gibt immer true zurück (Bedingung erfüllt) → Choice sichtbar

Warum

Wanderhändler: Tagesroll + markt.php-Integration

Was

  • setnewday.php _nd_events(): Wanderhändler-Roll ersetzt altes vendor-Setting
  • markt.php:
  • patch.sql: 3 neue Settings wanderhaendler_city, wanderhaendler_date, wanderhaendler_chance

Warum

markt.php: Stadt-abhängiger Markt-Hub mit NPC-Integration

Was

  • markt.php (Umbau): City-aware Navigations-Hub + NPC-Dialog-Integration
  • patches/current/seed_markt.php (neu): Seed für npc_descriptions (bela) + dialog_trees (bela/markt)
  • setnewday.php _nd_events(): $validCities auf Rassendörfer aktualisiert

Warum

Markt: Hotspot-Klicks öffnen NPC-Dialoge inline (Scene-Overlay)

Was

  • require/DialogSystem.php: render() in build() + render() refaktoriert
  • require/SceneHotspots.php: load() liest zusätzlich dialog_npc und dialog_context aus DB; beide Felder im return-Array
  • markt.php:
  • templates/layout/scene.php:

DB-Änderung

  • scene_hotspots: neue Spalten dialog_npc VARCHAR(60) + dialog_context VARCHAR(60)

Warum

Router: Whitelist-Fix für Counter-lose Navigation (Hotspots, Dialoge)

Was

  • core/Router.php allowVariants(): speichert jetzt auch die rohe URL ohne Counter in $session['allowednavs'], zusätzlich zur bisherigen Counter-Version
  • templates/layout/scene.php: doppeltes class=-Attribut auf <nav> behoben — scene-nav--fallback wurde wegen zwei class=-Attributen nie gesetzt, mobile JS fand das Element nicht

Warum

Hotspot-Editor (admin_hotspots.php) + scene.php Debug entfernt

Was

  • admin_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 fehlt
  • admin_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)

Warum

Topbar: game_topbar.php Partial — einheitlicher Header in info + scene

Was

  • 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-Zeile
  • templates/layout/info.php: eigener Header-Block durch <?php include __DIR__ . '/partials/game_topbar.php'; ?> ersetzt
  • templates/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> gesetzt

Warum

Dialog-Seeds: Krom · Caspar · Mara (vom Game Designer)

Was

  • patches/current/seed.php: drei neue Dialog-Bäume + npc_descriptions ergänzt

Warum

Szenen: NPC-Nav-Links (show_nav_links Toggle)

Was

  • scene_pages.show_nav_links TINYINT(1) NOT NULL DEFAULT 0 — neues Feld pro Szene
  • require/SceneHotspots.php: neue Methode sceneConfig(string $pageKey): array — lädt das Flag
  • markt.php: SceneHotspots::sceneConfig() aufrufen, $GLOBALS['sceneShowNavLinks'] setzen
  • templates/layout/scene.php:
  • admin_hotspots.php:
  • patch.sql: ALTER TABLE scene_pages ADD COLUMN IF NOT EXISTS show_nav_links …

Warum

Hotspot-Editor: dialog_npc/dialog_context beim Speichern erhalten

Was

  • 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)

Warum

Hotspot-Editor: Vorschau-Bild-Picker (rein visuell, nicht gespeichert)

Was

  • 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)

Warum

Fluchkessel: Sidebar/VFX-Update nach Fluch entfernen

Was

  • fluchkessel.php op=fluch_entfernen: AJAX-Antwort liefert jetzt vfx + sidebar_buffs

Warum

Dialog-JS: Leerer Bubble bei Action-Nodes behoben

Was

  • templates/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:

Warum

Markt: NPC-Avatare + Namens-Fix

Was

  • patches/current/patch.sql: NPC-Beschreibungen für alle 5 Marktstände (+ Dox) korrigiert

Warum

Burgschmiede: Schmiedemeister Theron (Dialog-NPC)

Was

  • burgschmiede.php (Neuentwicklung): ersetzt alten Platzhalter-Code vollständig
  • require/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-Label
  • patches/current/seed.php: Theron npc_description + dialog_trees (theron/schmiede)
  • patches/current/patch.sql: Setting schmiede_upgrade_kosten = 500 (Basispreis vor Rassen-Modifier)

Dialog-Pfade

  • introverbessern_check_waffe / verbessern_check_ruestung / keine_blaupause / rohstoffe / info_blaupausen / preis_erklaerung / gehen
  • verbessern_check_waffeverbessern_ja_waffe (Action → loop_to verbessern_done) | intro | bereits_verbessert
  • verbessern_check_ruestungverbessern_ja_ruestung (Action → loop_to verbessern_done) | intro | bereits_verbessert

Rassen-Aufschlag

Permanente Boni

Warum

Ruhmesliste als Dialog-Widget (Drek · Arena)

Was

  • require/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-empty
  • patches/current/seed.php: Drek-Dialog aktualisiert
  • patch.sql: ALTER TABLE characters ADD COLUMN IF NOT EXISTS battlepoints INT UNSIGNED NOT NULL DEFAULT 0

Datenquelle

Erweiterbarkeit

Arena: Hintergrundbilder Ring + Tribüne

Was

  • arena.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-Setting
  • patch.sql: Setting arena_kampf_aktiv = 0 (Initial-Wert; wird später vom Kampfsystem geschaltet)

Bild-Mapping

Warum

Arena: Kerker als interaktive Scene

Was

  • arena.php op=kerker (Hub): von Output::render('info') auf Output::render('scene') umgestellt
  • patch.sql: scene_pages für arena:kerker:edahnien (show_nav_links=1) + 6 scene_hotspots Einträge mit Platzhalter-Koordinaten (2×3 Grid)

Nächster Schritt

Die Arena (arena.php)

Was

  • arena.php (Neuentwicklung): ersetzt alten Platzhalter vollständig
  • pvparena.php (Redirect-Stub): leitet alle Requests transparent auf arena.php weiter; AJAX liefert JSON-Fehler mit Hinweis zum Neu-Laden
  • village.php: Nav-Link von pvparena.php auf arena.php umgestellt
  • patches/current/seed.php: 7 NPC-Descriptions (Drek + 6 Gladiatoren) + 7 Dialog-Bäume

Gladiatoren

Offene TODOs (folgen mit Kampfsystem)

  • gauntlet_heute-Flag: setnewday.php muss prefs['flags']['gauntlet_heute'] täglich löschen
  • Gauntlet + Gladiatoren-Modus: Implementierung nach Kampfsystem
  • Kopfgeld-Brett, Tribüne, Ruhmesliste: Implementierung nach Kampfsystem
  • Arena-NPC-Portraits: alle 7 avatar_path leer (Dateien fehlen noch)

Warum

mara.php → inn.php: Konsolidierung

Was

  • inn.php: alle Mara-Logik aus mara.php integriert
  • mara.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= umgestellt

Warum

DB-Fix: settings.setting VARCHAR(20) → VARCHAR(64)

Was

  • patch.sql: ALTER TABLE settings MODIFY COLUMN setting VARCHAR(64) NOT NULL
  • Truncated Schlüssel-Namen wurden per UPDATE auf die korrekten vollen Namen umbenannt (lokal bereits ausgeführt)

Warum

bautrupp.php → burgviertel.php (Merge)

Was

  • burgviertel.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ändert
  • docs/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)

Warum

  • Bautrupp und Grundstücksmakler gehören zusammen ins Burgviertel
  • Weniger Seitenwechsel für den Spieler
  • Dialog-JSON Action-URLs müssen auf den Handler zeigen der die AJAX-Ops ausführt

Seed nach Deploy

  • seed_burgviertel.php über Admin-Patch → Seeds einspielen ausführen (aktualisiert dialog_trees für siegbert/burgviertel + hannes/bautrupp)

burgviertel.php — Zimmererei als op-Zweig

Was

  • burgviertel.php (kein op): nur Siegbert, Bild edahnien_burgviertel.png
  • burgviertel.php?op=zimmererei: Hannes/Zimmererei, Bild edahnien_burgviertel_bautrupp.png
  • Neuer AJAX-Handler op=holzbalken_kaufen&qty=5|10|20 — nutzt Item::shopBuy()
  • Neuer extraVar holzbalken_preis aus item_definitions.price_gold
  • dialog_hannes_zimmererei.json: Holzbalken-Shop-Nodes + alle Bautrupp-Nodes, context zimmererei
  • Siegbert goto_bautrupp: jetzt navigate: burgviertel.php?op=zimmererei
  • bautrupp.php: Redirect auf burgviertel.php?op=zimmererei
  • houses.php: kein Haus → burgviertel.php (Siegbert), Bautrupp-Link/Plot-Redirect → burgviertel.php?op=zimmererei, allowPrefix statt allow für burgviertel
  • seed_burgviertel.php: lädt jetzt dialog_hannes_zimmererei.json mit context hannes/zimmererei

Warum

  • Zwei NPCs gleichzeitig auf einer Seite wirkt falsch
  • Zimmererei als op-Zweig: sauber, kein neues PHP-File nötig, zwei Bilder passen je nach op
  • Hannes bekommt eine eigene Rolle als Händler + Bautrupp in einem

Seed nach Deploy

  • seed_burgviertel.php via Admin-Patch → Seeds einspielen ausführen

Inventar-Popup Redesign — Rucksack Variant D

Was

  • Item::renderPopupHtml() komplett neu: erzeugt jetzt das vollständige .iv-window.iv-D HTML
  • Drei-Spalten-Layout: Kategorie-Sidebar (210px) · Karten-Grid (2-spaltig) · Detail-Panel (320px)
  • Sidebar-Filter per JS (Klick auf Kategorie blendet andere Karten aus)
  • Suchfeld: Echtzeit-Filter nach Item-Name
  • Klick auf Karte → Detail-Panel rechts aktualisiert (Vanilla JS)
  • Rarity-System: common / uncommon / rare / quest — Kartenrand + Icon-Farbe
  • Glyph-Icons: Cinzel-Font, 1–2 Zeichen pro Item (per Slug-Map oder auto)
  • Neue private Methoden: getSidebarCat(), getRarity(), getGlyph(), renderDetailHtml()
  • templates/layout/info.php: Google Fonts import (Cinzel, Cormorant Garamond, Cormorant SC, Inter)
  • templates/edahnien.css: Großer neuer Abschnitt Inventar/Rucksack Redesign (Variant D)

Warum

[0.0.5] – 2026-05-12

Was

Warum

Patch-Manager: Self-Update-Route

Was

  • admin_patch.php: neues op=pack_self — erstellt ZIP mit NUR admin_patch.php + manifest.json (type: self_update); früher Exit wie op=pack
  • buildSelfUpdateZip(): neue Hilfsfunktion für den Self-Update-ZIP-Aufbau
  • buildPatchZip(): admin_patch.php wird automatisch aus normalem Patch-ZIP ausgeschlossen (wie dbconnect.php)
  • Übersicht (op=''): Wenn admin_patch.php in files.txt → blaue Info-Box mit direktem "Self-Update-Paket erstellen"-Button
  • op=upload: erkennt manifest.type=self_update → kein GAME_VERSION-Update, eigene Erfolgsmeldung "Patch-Manager aktualisiert"
  • Nav: neuer Abschnitt "Patch-Manager" mit "Self-Update erstellen"-Link

Warum

VFX-Blur: Admin-Ausnahme + Buff-Tooltip

Was

  • core/Output.php: neue Methoden addBodyClass(string) / getBodyClasses() — beliebige CSS-Klassen an <body> setzen
  • templates/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 Zugangsschutz
  • templates/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-Info
  • inn.php AJAX: sidebar_buffs-Einträge bekommen tooltip-Feld
  • templates/partials/dlg-system.js: updateCharSidebar() setzt data-tooltip auf dynamisch erzeugten Buff-Zeilen

Warum

Inn: Ale-Dialog AJAX-Optimierung + Live Sidebar-Update

Was

  • templates/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-Aktion
  • inn.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 Seite
  • templates/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)
  • SQL: content_strings inn.php textid=20 bereinigt (Caspar-Sprechtext entfernt, Dialog übernimmt das)

Warum

Ausbildungslager-System

Was

  • require/Rewards.php NEU: Globales Balance-System für Belohnungen (EXP, Gold, Turns, Gems) aus settings-Tabelle
  • dbwrapper.php: Rewards.php global eingebunden
  • require/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 whitelisted
  • SQL: 4 neue characters-Felder (kunst_stufe, kunst_exp_invested, meisterkampf_stufe, meisterkampf_cooldown), Balance-Settings in settings-Tabelle

Warum

NPC-Editor & KI-Charaktere

Was

  • npc_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-Verwaltung
  • SQL: Tabelle npc_descriptions angelegt, 9 Ausbilder-NPCs als Seed eingespielt (Krom, Helga, Caelindor, Dolgrak, Zael, Peregrin, Skarra, Tiri, Aerin)

Warum

Rassen-System Umbau

Was

  • Alte race-Tabelle durch neue races-Tabelle ersetzt (JSON-Felder für Klassen, Varianten, Boni)
  • require/Race.php komplett neu geschrieben für die neue Tabellenstruktur
  • require/WizardHelper.php: RACES + RACE_KLASSEN Konstanten entfernt, neue loadRaces() Methode aus DB
  • wizard.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ügt
  • require/Character.php: neuer Getter appearanceVariant()
  • characters.appearance_variant TINYINT DEFAULT 0 Spalte hinzugefügt

Warum

Dialog-System (Phase 1)

Was

  • require/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 aus
  • dialog.php NEU: POST-Handler — CSRF-Check, Effekte (set_flag, give_exp, level_up-Platzhalter), Redirect
  • templates/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-Block
  • dialog_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äge
  • admin_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, Verwendung

Warum

Ausbildungslager — Hub-Erweiterung (NPC-Dialog + Sidebar-Nav)

Was

  • training.php Hub: 3 Stationslinks in der Sidebar-Navigation (Trainingsplatz, Ausbildungsraum, Meisterkampf) — doppelt zu den visuellen Karten, für konsistente Navigation
  • training.php Hub: Comic-Stil NPC-Dialog zwischen Intro-Text und Stationskarten
  • SQL: textid '40' (Stationen-Heading), textids '300'–'308' (NPC-Grüße pro Stadt)

Warum

Dialog-System — Erweiterungen (Cycle, can_level_up, Navigation, UX)

Was

  • templates/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-Übergangsblende
  • templates/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 HTML
  • templates/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)

Warum

training.php Sub-Seiten: Aktionen deaktiviert + Hintergrundbild

  • training.php: TRAINING_ACTIONS_DISABLED = true — Trainings-/Kauf-/Kampfbuttons ausgeblendet bis Kampfsystem integriert; Stats/Anforderungen bleiben sichtbar; auf false setzen zum Aktivieren
  • training.php basic/skill/master: Hintergrundbild aus templates/assets/poi/{city}_ausbildungslager.png (wie Hub)

training.php Sub-Seiten: Template + Navigation

  • training.php basic/skill/master: Output::render('interactive')'info' (alle Exits)
  • Navigation jetzt vollständig: ← Zurück zum Ausbildungslager, Heading Stationen mit Links zu allen drei, ← Zurück zum Burgplatz
  • $title für jede Sub-Seite gesetzt (Trainingsplatz / Ausbildungsraum / Meisterkampf)

training.php Hub: Stationskarten entfernt

  • 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).

Bugfix: Schwarzer Veil nach Dialog-Navigation bleibt

  • 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 wird
  • templates/layout/info.php + interactive.php: Veil-Script in IIFE, pageshow-Guard entfernt Veil bei bfcache-Restore der Zielseite

Schenke — Dialog-System (Caspar + Mara)

Was

  • inn.php komplett neu: DialogSystem::render('caspar','schenke'), Hintergrundbild, op-Handler (ale/essen/zimmer/geruechte/mara), Schankgespräche erhalten
  • inn.php Hub: Sidebar-Nav mit Heading "Angebote" + 5 Aktions-Links (Ale/Essen/Gerüchte/Zimmer/Mara) parallel zum Dialog
  • patches/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 $allownonnav
  • Caspar = Wirt (griesgraemig, wortkarg); Mara = Freudenherrin (selbstbewusst, kuehl)
  • Dialog-Fahrplan: Bestätigung für Essen + Zimmer, Gerüchte 2-stufig, Mara direkt "Frag sie."
  • Kosten-Labels auf Choices (dlg-system.js + edahnien.css): cost-Feld im Dialog-JSON → rechts ausgerichtet in der Choice-Button-Zeile
  • Tagesgericht setzt Flag inn_essen_heute (TODO: in newday.php zurücksetzen)
  • Alle Aktionen (Zimmer/Gerüchte/Mara) als Platzhalter — folgen mit Mechanik-System
  • Kosten-Platzhalter (1/2/5 Silber) — finales Balancing via settings-Tabelle

Warum

Schenke — Mechaniken (Gold, Buffs, Betrug, Zimmer)

Was

  • inn.php: Gold-Check für alle kostenpflichtigen Aktionen; Betrug-Option bei fehlendem Gold
  • Ale: Gold-Abzug (inn_ale_cost), bestehende Buff-Logik + Betrug möglich
  • Essen (1×/Tag): Gold-Abzug (inn_essen_cost); Buff tagesgericht (ATT+inn_essen_att_bonus, MaxHP+inn_essen_maxhp_bonus für inn_essen_turns Kämpfe); Tagesflag inn_essen_heute
  • Zimmer: Gold-Abzug (inn_zimmer_cost); setzt Flag inn_zimmer_sicher + Logout-Link; am Folgetag via newday.php: +inn_zimmer_extra_turns Runden + "Gut geschlafen"-Meldung
  • Betrug-Mechanik (?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)
  • Erfolgsformel: inn_betrug_base_chance + Schurke-Bonus (inn_betrug_dieb_bonus) + Charisma × inn_betrug_charm_per_point (max 95 %)
  • require/Character.php: charisma() Getter + setCharisma() Setter
  • newday.php (5b): inn_essen_heute-Flag zurücksetzen; Zimmer-Bonus anwenden (falls inn_zimmer_sicher gesetzt); "Gut geschlafen"-Ausgabe
  • core/charstats_func_inc.php: maxhp + Buff::statBonus($char, 'maxhp') (Essen-Buff sichtbar in LP-Leiste)
  • SQL: characters.charisma INT DEFAULT 0; 13 neue Balance-Settings für inn_*

Warum

Buff/Debuff-System

Was

  • 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ügt
  • inn.php Ale-Op: Staged Buff-Anwendung via Buff::drinkAle(), Tod bei Stage 5 (Redirect → shades.php), Flavor-Text + Buff-Info je nach Stage
  • newday.php: Buff-Verwaltung nach Revival (Block 4b): getAleStage (vor Remove), removeNewdayBuffs, remove ale_rausch, cleanup, applyKater wenn Stage ≥ 3; bei Tod via Revival: removeAll
  • core/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)
  • SQL: character_buffs Tabelle (character_id, slug UNIQUE, label, stage, stats JSON, vfx, expires_type ENUM, expires_at, invisible, data JSON)

Warum

Patch-Workflow — seed.php Support

Was

  • 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 vorhanden
  • patches/current/seed.php NEU: Krom/begruessung Dialog-Seed (ersetzt den SQL-String-Ansatz; sicher mit json_encode via PDO)

Warum

[0.0.3b] – 2026-05-10

Neu

  • Auto-Patch-System: patches/vX.Y.Z/patch.sql wird beim nächsten Request automatisch eingespielt — kein manuelles install.php mehr nötig
  • Patchnotes-Popup: Nach jedem Update erscheint einmalig ein Popup mit den Änderungen aus dem Changelog — für eingeloggte Spieler (DB-Tracking) und ausgeloggte Besucher (localStorage)
  • patch_log-Tabelle: verfolgt welche Patches bereits auf dem Server eingespielt wurden
  • characters.patch_seen: merkt pro Spieler welche Version er zuletzt gesehen hat

[0.0.3] – 2026-05-10

Neu

  • SPA-Wizard: Charaktererstellung ohne Seiten-Reload — alle 6 Schritte auf einmal geladen, JS navigiert zwischen Schritten, Portrait aktualisiert sich live bei Rassen-/Haarstil-Wahl
  • Versionsnummer auf jeder Seite beim Impressum; Klick öffnet Changelog-Popup
  • Portraits im Wizard deutlich größer (Race/Klasse: 320×480 px, Aussehen: 220×330 px, Zusammenfassung: 280×420 px)
  • install.php – Patch-SQL-Step: beliebige patches/vX.Y.Z/patch.sql direkt aus dem Browser einspielen

Behoben

  • require/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 → Badnav
  • require/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 ?? abgesichert
  • login.php: Logout leert jetzt vollständig via $_SESSION = [] + session_destroy() statt nur $session = []
  • register.php: Usernamen dürfen jetzt Ziffern enthalten; Catch auf \Throwable erweitert
  • Wizard onclick-Handler: json_encode() in Inline-Attributen erzeugte doppelt-gequotete Strings → Kampfkunst/Farben ließen sich nicht auswählen. Fix: einfache Anführungszeichen
  • Abbrechen-Button im Wizard-Reset ausgeblendet (kein Abbruch des Pflicht-Flows möglich)
  • Login-Formular: Input-Felder behielten falsche Hintergrundfarbe (CSS Cascade-Order-Fix)

Infrastruktur / SQL

  • user-Tabelle auf Live-Server fehlte → Registrierung schlug immer fehl. Tabelle + Migration in patch.sql
  • DB-Views accounts, accounts2, accounts_bio neu erstellt (referenzierten nach Aufräumarbeiten ungültige Spalten → General error: 1356)

[0.0.2] – 2026-05-09

Behoben

  • 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.

Infrastruktur

  • Patch-System eingeführt: patches/, CHANGELOG.md, deploy.bat
  • Arbeitsweise auf "lokal entwickeln → Patch deployen" umgestellt

[0.0.1] – 2026-05-09

Initial-Release (Basisstand)

  • PHP 8.2 Refactoring: TextRenderer, Router, Output + Shims
  • Neue Template-Architektur: landing / info / interactive / public
  • Charakter-Erstellung: WizardHelper, create.php, Klasse.php, Race.php
  • Login-System: Token-Rotation, Profiler, acclogin.php
  • Öffentliche Seiten: reich.php, bestiary.php, halloffame.php
  • Content-System: timeline→language, Content::get(), Output::getText()
  • Appearance-Migration: SQL + Character.php Getter
  • SQL-Schemas: schema_complete.sql, schema_items_quests.sql, schema_combat.sql
  • Feedback-System: feedback.php
  • Testserver-Migration: install.php, seed_views.sql, deploy.bat
  • Bugfixes: Router::isAllowed() Linux-Pfad-Normalisierung, Output.php Groß/Kleinschreibung, common.php accounts→characters
LIVE · REICHSKUNDE
Willkommen in Edahnien — dem Reich des Drachen.Erschaffe deinen Helden und tritt dem Abenteuer bei.Willkommen in Edahnien — dem Reich des Drachen.Erschaffe deinen Helden und tritt dem Abenteuer bei.