ff14-mitigator/CLAUDE.md
xziino e85e341b33 Move boss debuffs to AoE event header, update CLAUDE.md
Debuffs (Reprisal, Feint, Addle, etc.) now appear once next to the
ability name and REF label instead of repeating under each player card.
Flex layout on .aoe-ability matches the REF row spacing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 18:42:50 +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)
  • Darstellung: 16×16px Icon unter jedem Spieler-Target, kein Text, title-Tooltip mit Name + DR% + Caster

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
  • Schaden-Delta pro Spieler: grün wenn besser (aoe-delta-better), rot wenn schlechter (aoe-delta-worse)
  • Gesamt-Delta in der REF-Headerzeile

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)