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

219 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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:
```js
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 `targetID`s
- 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 `abilityGameID`s 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)