2 Helden online · 3 registriert
— Das Reich Edahnien —
Eine Reise durch die Lande
Willkommen in Edahnien Edahnien ist ein Reich am Rande des Vergessens. Einst ein blühendes Land voller Händler und Abenteurer, liegt es heute im Schatten des Grünen Drachen. Die Stadt Aiviris Im Herzen des Reiches liegt Aiviris — eine mittelalterliche Stadt mit einem geschäftigen Marktplatz, einem schimmernden Gasthaus und einem Tempel der Stillen Mutter. Der verfluchte Wald Jenseits der Stadtmauern beginnt der verfluchte Wald. Wölfe, Goblins und schlimmere Wesen lauern zwischen den Bäumen. Nur die Tapfersten wagen sich bis zum Hort des Drachen vor. Der Grüne Drache Am Ende eines langen Weges wartet er. Der Grüne Drache — uralt, mächtig und unbesiegbar. Noch. Vielleicht bist du der Held, der sein Ende bringt.
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.Willkommen in Edahnien.