ff14-mitigator/CLAUDE.md
xziino 5345927b83 Update CLAUDE.md: document debuff header placement and icon split logic
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 18:50:01 +02:00

12 KiB
Raw Blame History

ff14-mitigator — FFLogs Mitigation Analyzer

Projekt

PHP/HTML/JS-Tool zum Analysieren von FFXIV-Raidlogs via FFLogs OAuth2 PKCE + GraphQL API. Kein Framework, kein Composer, kein npm — Plain PHP für Shared Hosting.

Zwei Tabs:

  • Report-Tab: Report-Code eingeben, Fight auswählen → Fight-JSON-Ausgabe + Event Explorer
  • Analyse-Tab: Spielerübersicht + AoE-Timeline mit Mitigation-Tracking, Pull-Vergleich, Phase-Filter

Architektur & Konventionen

Trennung von PHP, HTML und JS

  • PHP-Logik gehört ausschließlich in index.php (und API/Auth-Endpunkte). Keine Geschäftslogik in Templates.
  • HTML gehört in templates/. Jede logisch in sich geschlossene Komponente ist eine eigene Datei.
  • CSS gehört in css/. Jede CSS-Datei hat einen klar abgegrenzten Scope (base, layout, components, analysis).
  • JavaScript gehört in js/. Keine Inline-Scripts in Templates außer dem <script src="..."> Tag in page.php.

Template-System

index.php setzt alle Variablen und ruft dann require templates/page.php auf. Templates sind reine Ausgabe — sie lesen Variablen aus dem Scope, setzen aber keine.

Geteilter JS-State

window.App in app.js hält den gemeinsamen State für alle Tabs:

window.App = { reportCode, fightId, fightStart, fightEnd, phases: [], fights: [] }

window.analysisTab (definiert in analysis.js) stellt Hooks bereit:

  • onFightSelected() — wird von app.js aufgerufen wenn ein Fight gewählt wird
  • onTabOpen() — wird von tabs.js aufgerufen wenn der Analyse-Tab geöffnet wird
  • onFightsLoaded(fights) — wird von app.js aufgerufen nach Report-Load (befüllt Vergleichs-Dropdown)
  • reset() — wird von app.js aufgerufen wenn ein neuer Report geladen wird

Dateistruktur

index.php                   — PHP-Logik: Auth-Check, Variablen, require page.php
config.php                  — Konstanten (CLIENT_ID, URIs) + session_start_safe()
templates/
  page.php                  — HTML-Skeleton (head, body), routet zu login oder app
  login.php                 — Login-Overlay (nicht authentifiziert / Token abgelaufen)
  topbar.php                — Topbar mit Logo + Tab-Navigation + Token-Ablaufzeit
  tab-report.php            — Report-Tab: includes report-form, fight-select, event-explorer, output-card
  tab-analysis.php          — Analyse-Tab: Spieler-Grid + Pull-Vergleich + AoE-Timeline HTML
  report-form.php           — Report-Code-Eingabe Card
  fight-select.php          — Fight-Auswahl Dropdown Card
  event-explorer.php        — Event Explorer Card (Ability/DataType/EventType/Spieler-Filter)
  output-card.php           — Terminal-Ausgabe Card + Initial-Hint
css/
  base.css                  — CSS-Variablen, Reset, Basis-Styles, Feedback-Klassen
  layout.css                — App-Shell, Topbar, Tabs, Login-Overlay, Form-Helpers
  components.css            — Cards, Inputs, Buttons, Badges, Terminal
  analysis.css              — Spieler-Grid, AoE-Timeline, Mitigation-Icons, HP-Bar, Ref-Row
js/
  app.js                    — Formular, Fight-Dropdown, Fetch, window.App State, Event Explorer
  tabs.js                   — Tab-Switching, ruft window.analysisTab.onTabOpen() auf
  analysis.js               — Analyse-Tab: Daten laden, Spieler rendern, Timeline rendern, Pull-Vergleich
auth/
  start.php                 — PKCE generieren, Session speichern, Redirect zu FFLogs
  callback.php              — Code gegen Token tauschen, Token in Session speichern
api/
  fight.php                 — POST-Endpunkt: Fight-Liste via GraphQL → JSON
  analysis.php              — POST-Endpunkt: Spieler + AoE-Events + Mitigations → JSON
  abilities.php             — POST-Endpunkt: Ability- + Spielerliste für Event Explorer Dropdowns
  debug-events.php          — POST-Endpunkt: Raw Events für Event Explorer (mit Filterung)
assets/
  icons/mitigation/         — Lokal gespeicherte Ability-Icons (PNG, von XIVAPI)
debug/
  schema.php                — Einmaliges Schema-Explorer Tool (nicht produktiv deployen)

Design-System

CSS-Variablen in css/base.css:

  • Hintergründe: --bg0 (#08090d) bis --bg3 (#1c2130), --bgcard (#121620)
  • Akzentfarbe: --gold (#c8a84b)
  • Text: --t1 (hell) / --t2 (gedimmt) / --t3 (sehr gedimmt)
  • Farben: --blue, --green, --red, --orange
  • Fonts: --font-d Cinzel (Titel/Logo), --font-b Inter (Body)
  • Border-Radius: --r (klein), --rl (groß)

Analyse-Tab — Konzepte & Entscheidungen

AoE-Erkennung

  • Verwendet damage Events mit includeResources: true (enthält buffs-Feld für Mitigations)
  • Tick-Schaden (ev['tick'] = true) und abilityGameID ≤ 7 (Auto-Attacks) werden gefiltert
  • Proximity Clustering statt fixer Zeitfenster: Events werden pro abilityGameID gesammelt, dann in Cluster eingeteilt — ein neuer Cluster beginnt wenn der Abstand zum ersten Event im aktuellen Cluster > CLUSTER_WINDOW_MS (1000ms) beträgt
  • AoE = Cluster mit ≥ 3 unterschiedlichen targetIDs
  • Pro Target werden amount, absorbed, overkill, hp (nach Treffer), maxHp und buffs gespeichert; bei mehreren Hits desselben Targets pro Cluster werden amount/absorbed/overkill summiert

Spielernamen statt IDs

  • masterData.abilities (gameID → Name) wird zusammen mit playerDetails in einem einzigen Query abgerufen
  • $mitigIdMap (gameID → Mitigation-Meta) wird nur für Abilities gebaut, die tatsächlich im Report vorkommen

Mitigation-Tracking

Getrackte party-wide Buffs + Boss-Debuffs (definiert in MITIGATION_ABILITIES in api/analysis.php):

Ability DR Typ
Passage of Arms 15% buff
Divine Veil Barrier buff
Shake It Off Barrier buff
Dark Missionary 10% buff
Heart of Light 10% buff
Temperance 10% buff
Sacred Soil 10% buff
Expedient 10% buff
Fey Illumination 5% buff
Collective Unconscious 10% buff
Holos 10% buff
Kerachole 10% buff
Panhaima Barrier buff
Troubadour 15% buff
Tactician 15% buff
Shield Samba 15% buff
Magick Barrier 10% buff
Reprisal 10% debuff
Feint 10% debuff
Addle 10% debuff

Implementierung via buffs-Feld: Mitigations werden nicht über separate applybuff/removebuff-Events getrackt, sondern direkt aus dem buffs-Feld jedes damage-Events gelesen. Das Feld enthält einen .-separierten String von abilityGameIDs der aktiven Buffs. resolveMitigations() mappt diese IDs auf MITIGATION_ABILITIES-Einträge via $mitigIdMap (gameID → Meta). Doppelte Namen werden dedupliziert.

Mitigation-Icons

  • Icons lokal gespeichert in assets/icons/mitigation/ als PNG
  • Quelle: XIVAPI v2 (https://v2.xivapi.com/api/asset?path=...&format=png)
  • Icon-Pfade per Action-Row-ID abgerufen: https://v2.xivapi.com/api/sheet/Action/{id}?fields=Name,Icon
  • Dateinamen: kebab-case des Ability-Namens (z.B. passage-of-arms.png)
  • Mapping in analysis.js: MITIG_ICONS Objekt (Ability-Name → lokaler Pfad)

Darstellungsort nach buffType:

  • Buffs (buffType: 'buff'): erscheinen per Spieler unter der Spieler-Box (.aoe-target-buffs)
  • Debuffs (buffType: 'debuff'): erscheinen einmal pro Event im Ability-Header neben "X total" bzw. in der REF-Zeile neben "REF X total ±delta"
  • Fehlende Mitigations aus dem Ref-Pull: Buffs → ausgegraut per Spieler; Debuffs → ausgegraut im jeweiligen Header
  • .aoe-ability nutzt display: flex; align-items: center; gap: 6px — identisches Spacing wie .aoe-ref-label

Implementierte Features (Übersicht neuerer Commits)

HP-Balken pro Spieler

2-Segment-Balken direkt unter Name+Schaden in der Spieler-Box:

  • Grün (links): HP nach dem Treffer — Farbton dynamisch: >50% grün, 2550% amber, <25% rot
  • Rot (Mitte): erlittener Schaden (als % von MaxHP)
  • Nur gerendert wenn t.maxHp > 0; Daten kommen aus targetResources.hitPoints / maxHitPoints via includeResources: true

Death-Highlight + Absorbed/Overkill

  • Spieler-Box bekommt Klasse .aoe-target--dead wenn hp === 0 && maxHp > 0
  • Overkill-Betrag wird links neben dem Job-Badge angezeigt (.aoe-target-overkill)
  • Absorbed-Betrag erscheint als gedimmter Zusatz neben dem Schaden (.aoe-target-absorbed)

Phase-Filter

  • Liest phaseTransitions aus dem gewählten Fight-Objekt (window.App.phases)
  • Dropdown #phase-select erscheint nur wenn Phasen vorhanden sind
  • Option "Ganzer Fight" (id=0) ist immer dabei; individuelle Phasen ab id=1
  • phaseFilter = { startTime, endTime } filtert die Timeline in renderTimeline()

Spieler-Sortierung

Konsistentes Healer → DPS → Tank-Ordering überall: im Spieler-Grid, in jedem AoE-Event, und in der Referenz-Zeile. Innerhalb gleicher Rolle alphabetisch nach Name.

Pull-Vergleich (innerhalb desselben Reports)

  • Dropdown #ref-fight-select wird nach Report-Load mit allen Fights befüllt (via onFightsLoaded)
  • Bei Auswahl: separater api/analysis.php-Call für den Ref-Fight → refEvents[]
  • In renderTimeline(): per abilityName und Occurrence-Index gematchter Ref-Event wird als REF-Zeile unterhalb der aktuellen Targets gerendert
  • Fehlende Mitigations (vorhanden im Ref, nicht im aktuellen Pull) werden als ausgegrautem Icon mit Klasse .aoe-buff-missing gezeigt — Buffs per Spieler, Debuffs im Ability-Header
  • Schaden-Delta pro Spieler: grün wenn besser (aoe-delta-better), rot wenn schlechter (aoe-delta-worse)
  • Gesamt-Delta + Ref-Debuff-Icons in der REF-Headerzeile (aoe-ref-label)

Cross-Report-Vergleich

  • Button "+ Anderer Report" (#ref-ext-toggle) öffnet Panel mit Eingabefeld + Laden-Button
  • api/fight.php wird mit dem externen Report-Code aufgerufen → Fight-Dropdown befüllt
  • Auswahl eines externen Fights → api/analysis.php-Call mit externem Code → refEvents[]
  • Same-Report- und Cross-Report-Selektion schließen sich gegenseitig aus (jeweils Reset des anderen Dropdowns)

Event Explorer (im Report-Tab)

Erscheint nach Fight-Auswahl als Card über dem Output-Terminal. Filteroptionen:

  • Ability: Dropdown mit allen NPC-Fähigkeiten des Fights (via api/abilities.php)
  • DataType: DamageTaken / DamageDone / Healing / Casts / Buffs / Deaths
  • Event-Typ (raw): damage, calculateddamage, applybuff, removebuff, etc.
  • Spieler: Dropdown mit allen Spielern des Fights (via api/abilities.php)
  • Limit: 1500 Events
  • Von/Bis: Zeitoffsets in Sekunden vom Fight-Start
  • Output landet im gleichen #output-Terminal wie der Fight-JSON
  • Backend api/debug-events.php: filtert Events nach Typ, Ability-ID und Spieler (Source oder Target); gibt Metadaten + gefilterte Events zurück

Konfiguration

  • config.php: CLIENT_ID, REDIRECT_URI, DEV_MODE anpassen
  • DEV_MODE = true deaktiviert SSL-Verifizierung (nur lokal, nie in Produktion)
  • session.cookie_secure ist bei DEV_MODE automatisch false

FFLogs API

  • OAuth2 PKCE (kein Client Secret, öffentliche App)
  • App registrieren: https://www.fflogs.com/api/clients/
  • GraphQL Endpoint (user-scoped): https://www.fflogs.com/api/v2/user
  • Token Endpoint: https://www.fflogs.com/oauth/token
  • Kein Refresh Token für öffentliche Clients — abgelaufene Sessions starten PKCE neu
  • Event-Typen: damage (post-Mitigation Snapshot, enthält buffs-Feld — den wir nutzen) vs. calculateddamage (Application-Zeitpunkt, ignorieren)
  • includeResources: true im events()-Query liefert targetResources.hitPoints / maxHitPoints pro Event

XIVAPI

  • Basis-URL: https://v2.xivapi.com
  • Action-Lookup per Row-ID: /api/sheet/Action/{id}?fields=Name,Icon
  • Asset-Download: /api/asset?path={tex_path}&format=png
  • Icons nicht hotlinken — lokal speichern (Community-Service, kein SLA)
  • XIVAPI-Suche (/api/search) gibt bei manchen Abilities ClassJob-Daten statt Action-Daten zurück → direkt per Row-ID abrufen

Repository

  • Remote: https://git.epow0.org/xziino/ff14-mitigator
  • Platform: Gitea (git.epow0.org)
  • Branch: main

Lokale Entwicklung

php -S localhost:8080

Dann http://localhost:8080 im Browser öffnen. Redirect URI in FFLogs App und config.php: http://localhost:8080/auth/callback.php

Bekannte Schema-Infos (ReportFight)

Verfügbare aber noch nicht genutzte Felder: friendlyPlayers, enemyNPCs, lastPhase, standardComposition, hasEcho, combatTime Genutzt: phaseTransitions (für Phase-Filter), fightPercentage, kill, startTime, endTime Vollständiges Schema: siehe debug/schema.php oder fflogs-schema.json

Deployment

  • DEV_MODE auf false setzen
  • REDIRECT_URI auf produktive HTTPS-URL anpassen
  • debug/ Ordner nicht deployen
  • assets/ Ordner deployen (enthält lokale Icons)