ff14-mitigator/CLAUDE.md
xziino 58745fec65 CLAUDE.md: Planer-Dokumentation auf aktuellen Stand gebracht
Datenmodell, Roadmap, Gantt-Entscheidungen, Äquivalente-Ort und
Recast-Format aktualisiert; veraltete Punkte entfernt.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 16:33:39 +02:00

399 lines
24 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.
Drei 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
- **Planer-Tab** *(in Entwicklung)*: Cooldown-Planer für Raid-Mitigation — Log-Import, manuelle Bearbeitung, Job-basierte Spell-Verfügbarkeit, DR-Simulation
## 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
`window.plannerTab` (definiert in `planner.js`) stellt Hooks bereit:
- `onTabOpen()` — wird von `tabs.js` aufgerufen wenn der Planer-Tab geöffnet wird
- `importFromAnalysis(aoeEvents, refEvents, options)` — wird vom Analyse-Tab aufgerufen beim Export
## 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
tab-planner.php — Planer-Tab: Plan-Liste, Mechanik-Timeline, Job-Aufstellung (in Entwicklung)
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
planner.css — Planer-Tab: Plan-Liste, Mechanik-Cards, Job-Slots, Ability-Modal (in Entwicklung)
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
planner.js — Planer-Tab: localStorage CRUD, Plan-Rendering, Ability-Zuweisung (in Entwicklung)
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)
data/
recast-times.json — Recast-Zeiten pro Ability (in Entwicklung)
ability-equivalents.json — Funktionale Äquivalente über Jobs hinweg (in Entwicklung)
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 aus zwei Quellen befüllt:
1. `masterData.abilities` — für Abilities die als Events im Report auftauchen (Name-basiertes Matching)
2. Statische `statusId`-Felder in `MITIGATION_ABILITIES` — Fallback für Abilities die nicht in masterData stehen (z.B. Pre-Pull-Buffs)
### Mitigation-Tracking
Getrackte party-wide Buffs + Schilde + Boss-Debuffs (definiert in `MITIGATION_ABILITIES` in `api/analysis.php`):
Drei `buffType`-Kategorien:
- **`buff`**: Damage Reduction — Passage of Arms (15%), Troubadour/Tactician/Shield Samba (15%), Dark Missionary/Heart of Light/Temperance/Sacred Soil/Expedient/Collective Unconscious/Holos/Kerachole/Magick Barrier (10%), Fey Illumination (5%)
- **`shield`**: Barrieren — Divine Veil, Guardian, Shake It Off, Bloodwhetting, Divine Benison, Divine Caress, Intersection, Neutral Sect, the Spire, Panhaima, Holosakos, Eukrasian Prognosis, Eukrasian Diagnosis, Differential Diagnosis, Haima, Galvanize, Seraphic Veil, Catalyze, Radiant Aegis, Tempera Coat, Tempera Grassa, Improvised Finish
- **`debuff`**: Boss-Debuffs — Reprisal, Feint, Addle (je 10%)
**Statische Status-IDs (`statusId`-Feld):** Jeder Eintrag in `MITIGATION_ABILITIES` trägt ein `statusId`-Feld (FFLogs Status-ID = XIVAPI Status row_id + 1.000.000). Diese IDs werden als Fallback in `$mitigIdMap` eingetragen wenn masterData den Eintrag nicht enthält. Löst das Pre-Pull-Problem und Name-Mismatches (z.B. FFLogs "Guardian's Will" vs. Key 'Guardian', "Desperate Measures" vs. 'Expedient').
**Primär: `buffs`-Feld im `damage`-Event:** Das Feld enthält einen `.`-separierten String aktiver Status-IDs. `resolveMitigations()` mappt diese via `$mitigIdMap`. Doppelte Namen werden dedupliziert.
**Fallback: Shield-Timeline via `applybuff`/`removebuff`:** Das `buffs`-Feld des `damage`-Events spiegelt den Zustand *nach* dem Hit — Schilde die durch den Hit konsumiert wurden fehlen darin. Daher wird in Step 2b ein separater Buffs-Query (dataType: Buffs) für alle Shield-StatusIds gefetcht und eine Timeline aufgebaut: `$shieldTimeline[targetId][statusId][] = ['apply' => ts, 'remove' => ts|null]`. Bei `absorbed > 0` wird `shieldsActiveAt()` konsultiert und alle dort gefundenen Schilde die noch nicht in der Mitigation-Liste stehen werden hinzugefügt (Name-Deduplication). Buffer: 200ms (`remove >= damageTs - 200`) um consumed-at-impact von natural-expiry zu unterscheiden.
### 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`)
- **Schilde** (`buffType: 'shield'`): erscheinen als Tooltip auf dem `+absorbed`-Wert (`.aoe-target-absorbed`) — Liste der aktiven Shield-Namen, consumed Schilde werden über die Timeline-Fallback ergänzt
- **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) vs. `calculateddamage` (Snapshot beim Cast-Start buffs sind zu früh, aber `targetResources.absorb` zeigt den Schildwert zu diesem Zeitpunkt)
- `buffs`-Feld im `damage`-Event: `.`-separierter String aktiver Status-IDs. Achtung: Schilde die durch diesen Hit konsumiert wurden sind bereits entfernt Shield-Timeline als Fallback nötig
- FFLogs Status-IDs = XIVAPI Status-Sheet `row_id` + 1.000.000 (z.B. Galvanize: row_id 297 FFLogs 1000297)
- `dataType: Buffs` liefert `applybuff`/`removebuff`-Events (u.a. für die Shield-Timeline)
- `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)
---
## Planer-Tab — Konzept & Roadmap
### Ziel
Raid-Cooldown-Planer: Welche Mitigation-Ability wird für welche Mechanik eingesetzt? Basierend auf Log-Daten oder manuell aufgebaut. Überlebt Browser-Neustarts via localStorage. Kein Server-State alles im Browser.
### Datenmodell (Plan)
```json
{
"id": "uuid",
"name": "M8S Prog Week 1",
"createdAt": 1234567890,
"updatedAt": 1234567890,
"source": { "reportCode": "abc123", "fightId": 6, "fightName": "Howling Blade", "fightStart": 0, "fightEnd": 420000, "language": "en" },
"mitigationNames": { "Reprisal": "Vergeltung" },
"folderId": null,
"jobComposition": ["PLD", "WAR", "WHM", "SCH", "MNK", "DRG", "BRD", "SMN"],
"mechanics": [
{
"id": "uuid",
"name": "Fourth-Wall Fusion",
"abilityId": 12345,
"timestamp": 83000,
"phase": "Phase 1",
"unmitigatedDamage": 280000,
"notes": "",
"assignments": [
{ "ability": "Reprisal", "abilityName": "Vergeltung", "job": "PLD", "buffType": "debuff" },
{ "ability": "Shield Samba", "abilityName": "Schildsamba", "job": "BRD", "buffType": "buff" }
]
}
]
}
```
Mehrere Pläne gespeichert in `localStorage` unter `ff14-planner-plans` als Array.
Ordner gespeichert unter `ff14-planner-folders`. Pläne referenzieren Ordner per `folderId`.
Aktiver Plan per `ff14-planner-active-plan` (ID) wird beim Tab-Öffnen wiederhergestellt.
### Primärer Import-Flow: Export aus dem Analyse-Tab
Der Haupteinstieg ist der Analyse-Tab der Nutzer hat die Daten bereits geladen und sieht die Timeline.
1. Button **"In Planer exportieren"** erscheint im Analyse-Tab sobald Daten geladen sind (auch für den Ref-Log separat)
2. Dialog mit zwei Entscheidungen:
- **Was importieren?** "Nur Mechaniken" vs. "Mechaniken + erkannte Mitigations als Startpunkt"
- **Wohin?** Neuen Plan anlegen (Name eingeben) vs. bestehenden Plan überschreiben/mergen (Dropdown)
3. Bei Merge: explizite Bestätigung niemals implizit überschreiben
4. AoE-Events werden zu Mechaniken; Phase-Information aus `phaseTransitions` wird mitübernommen
5. Weiterarbeiten im Planer-Tab
**Merge-Logik:** Mechaniken gelten als identisch wenn `|timestamp_a - timestamp_b| < 1500ms`. Nur neue Mechaniken werden hinzugefügt, bestehende Assignments bleiben erhalten. Neue Mechaniken werden timestamp-sortiert eingefügt.
**Warum Merge statt Überschreiben:** Progress-Szenario erster Import enthält Phase 1, späterer Import fügt Phase 2 hinzu ohne Phase-1-Planung zu verlieren.
**Sprachlokalisierung:** `api/analysis.php` gibt `mitigation_names` (key lokalisierter Name) zurück. Plan speichert diese in `mitigationNames`. `refreshPlanLanguage()` in `planner.js` aktualisiert Namen wenn Sprache wechselt (Event `ff14-language-change`). Anzeigename via `assignmentAbilityName(assignment, plan)`.
### Implementierungs-Reihenfolge
| Schritt | Status | Feature |
|---|---|---|
| 1 | | **Datenfundament** Plan-Datenmodell + localStorage CRUD |
| 2 | | **Tab-Grundgerüst** Plan-Liste, Ordner, Mechanik-Timeline |
| 3 | | **Import aus Analyse-Tab** Export-Button + Dialog + Merge |
| 4 | | **Jobaufstellung** 8 Slots mit Job-Dropdown + Rollenfärbung |
| 5 | | **Ability-Zuweisung** Modal-Picker + Rechtsklick-Remove + Äquivalenz-Hints |
| 6 | 🔜 | **Gantt-Chart** Recast-Konflikte + DR%-Anzeige (s. unten) |
| 7 | 🔜 | **Analyse-Overlay** geplante vs. tatsächlich genutzte CDs |
### Gantt-Chart — Design-Entscheidungen (Schritt 6)
**Konzept:** Ergänzende Ansicht zur Mechaniken-Übersicht, nicht Ersatz. Umschaltbar per Toggle/Tab.
**Layout:**
- X-Achse: Kampfzeit (0 bis Kampfende aus `source.fightEnd - source.fightStart`)
- Linke Spalte: Ability-Icons (eine Zeile pro eingesetzter Ability im Plan) Zeilengranularität TBD
- Vertikale Linien: Mechaniken-Timestamps
- Farbige Balken: Ability aktiv (Dauer) + Cooldown-Bereich danach (gedimmt)
- Konflikte: wenn ein Balken eine Mechaniken-Linie überlappt = visuell hervorgehoben
**Drag & Drop:**
- Ability-Icons aus Palette auf Timeline ziehen Icon am Startpunkt, Balken zeigt Dauer
- Freies Ziehen ohne Snapping Timestamp wird aus X-Position berechnet
- Bestehende Balken sind ebenfalls verschiebbar
**Update-Button:**
- Gleicht Gantt-Positionen mit Mechaniken ab
- Matching-Logik: TBD (Overlap oder nächste Mechanik) noch zu klären
- Aktualisiert Plan-Assignments entsprechend
**DR-Simulation:**
- Pro Mechanik: `simulierter Schaden = unmitigiert × ∏(1 dr_i)` für alle zugewiesenen Buffs/Debuffs
- Wird in der **Mechaniken-Übersicht** unter dem unmitigierten Schadenswert angezeigt
- Schilde werden **nicht** simuliert (Schildwerte nicht verlässlich aus Log ableitbar)
- Kein Pass/Fail-Urteil nur der errechnete Zahlenwert, User entscheidet selbst
**Recast-Daten:** `data/recast-times.json` noch zu befüllen. Enthält Cooldown-Dauer (s) und Aktiv-Dauer (s) pro Ability:
```json
{ "Reprisal": { "recast": 60, "duration": 10 }, "Feint": { "recast": 90, "duration": 10 } }
```
### UI-Paradigma
- Visuell dem Analyse-Tab ähneln (Cards, gleiche CSS-Variablen, einheitliches Look & Feel)
- Mechaniken als vertikale Timeline-Cards primäre Bearbeitungsfläche bleibt erhalten
- Ability-Picker als **Modal** (kein Inline-Dropdown)
- Gantt als zusätzliche Ansicht für den Planer (derjenige der die CDs koordiniert)
- Nicht für mobile Geräte ausgelegt
### Spell-Verfügbarkeit nach Job
Jobaufstellung verfügbare Abilities (Subset von `MITIGATION_ABILITIES`):
| Job | Abilities |
|---|---|
| PLD | Passage of Arms, Divine Veil, Guardian, Reprisal |
| WAR | Shake It Off, Bloodwhetting, Reprisal |
| DRK | Dark Missionary, Reprisal |
| GNB | Heart of Stone *(noch nicht getrackt)*, Reprisal |
| WHM | Temperance, Divine Benison, Divine Caress |
| SCH | Sacred Soil, Expedient, Fey Illumination, Galvanize, Seraphic Veil, Catalyze, Addle |
| AST | Collective Unconscious, Neutral Sect, Intersection, the Spire |
| SGE | Kerachole, Holos, Holosakos, Panhaima, Eukrasian Prognosis, Eukrasian Diagnosis, Haima, Addle |
| BRD | Troubadour |
| MCH | Tactician |
| DNC | Shield Samba, Improvised Finish |
| MNK | Feint |
| DRG | Feint |
| NIN | Feint |
| SAM | Feint |
| RPR | Feint |
| VPR | Feint |
| BLM | Addle |
| SMN | Addle, Radiant Aegis |
| RDM | Addle, Magick Barrier |
| PCT | Addle, Tempera Coat, Tempera Grassa |
### Job-Äquivalente (in `planner.js` als `ABILITY_EQUIVALENTS`)
Abilities die funktional gleich sind aber unterschiedliche Namen haben zeigt Hinweis bei fehlendem Job.
Kein automatischer Austausch nur Hinweis in rot/grün unter dem ausgegrauten Badge.
| Gruppe | Abilities |
|---|---|
| 15% Party-Mitigation | Troubadour, Tactician, Shield Samba |
| 10% Ground-Barrier | Sacred Soil, Kerachole |
Reprisal, Feint und Addle sind identische Ability-Namen über Jobs hinweg kein Mapping nötig.
### `extraAbilityGameID` in MITIGATION_ABILITIES (geplant)
Jeder Eintrag in `MITIGATION_ABILITIES` soll ein `actionId`-Feld bekommen die echte Action-Row-ID aus FFLogs (`applybuff`-Event `extraAbilityGameID`). Diese ID entspricht direkt der XIVAPI Action-Sheet-Row-ID und ermöglicht automatischen Icon-Lookup:
- 5-stellige IDs: führende 0 ergänzen falls XIVAPI 6 Stellen erwartet
- Lookup: `https://v2.xivapi.com/api/sheet/Action/{actionId}?fields=Name,Icon`
- Ermöglicht künftig automatisches Icon-Fetching statt manueller `MITIG_ICONS`-Pflege
### Recast-Zeiten (`data/recast-times.json`)
Format: `{ "Ability": { "recast": 60, "duration": 10 } }` noch zu befüllen.
Bekannte Werte (Beispiele):
- Reprisal: recast 60s, duration 10s
- Feint / Addle: recast 90s, duration 10s
- Dark Missionary / Heart of Light: recast 90s, duration 15s
- Troubadour / Tactician / Shield Samba: recast 120s, duration 15s
- Temperance: recast 120s, duration 20s
- Sacred Soil / Kerachole: recast 30s, duration 15s
### Technische Entscheidungen
- **Persistenz:** `localStorage` kein Backend nötig
- **IDs:** `crypto.randomUUID()` für Plan-, Mechanik- und Ordner-IDs
- **Eindeutige Namen:** `uniquePlanName()` verhindert Duplikate beim Erstellen und Importieren
- **Keine Spielernamen:** Assignments sind Job-basiert (`{ ability, job }`), damit Pläne übertragbar sind
- **Kein Ability-Stacking:** FFXIV erlaubt keine doppelte Anwendung derselben Ability pro Mechanik
- **Shield-Attribution:** Nicht simulierbar `absorbed` ist Gesamtwert ohne Aufschlüsselung. Bewusst weggelassen.
- **DR-Simulation:** Nur Buffs/Debuffs mit bekanntem `dr`-Wert aus `MITIGATION_ABILITIES`. Ergebnis als Zahlenwert, kein Pass/Fail.
- **fight.php:** Immer englischer Endpoint (`www.fflogs.com`) für stabile Fight-Namen unabhängig von Spracheinstellung