30.07.2026 — WOS Calculator & Event Voting Reaches the State Containers
Calculator & Voting Available in Custom-State Containers
- The WOS Upgrade Calculator is now deployed in the `mustervorlage` container too, so newly cloned state containers get the same three calculators (Buildings, Fire Crystal, War Academy) plus trust badges and drift disclaimer.
- The new Event Voting page (`/event-voting.php`) replaces the old mushroom popup in containers as well — full search wall, video previews, vote and date-polls work identically.
- All five `event_poll_*` tables have been created in the container SQLite so admin rounds, proposals, votes and audit logs work out of the box.
Klone-Workflow
- New clones of `mustervorlage` inherit these features automatically via `clone_from_mustervorlage.sh` step 4b (`docker diff`) — no extra setup required.
- Existing clones (`huette`, `muster-test-2`) still need a manual sync if the features are wanted there.
29.07.2026 — Mushroom Popup Removed, Dedicated Event Voting Page
New Event Voting Page
- The animated mushroom launcher that floated in the corner of every public page has been completely removed.
- All voting logic now lives on its own page: `/event-voting.php` — no overlay, no animation, no surprise growth on every second page.
- Two-step flow is the same as before: Find your player → Vote for events.
- The voting modal content (search wall, proposals, date options, video previews) is now embedded directly in the page.
What is still the same
- Search by name or FID, FID confirmation against `player_db`, one vote per event, 50-vote threshold, admin-managed rounds & phases, video preview inline in each proposal card.
- Backend `lib/event_poll.php`, `api/event-popup-{state,vote,player-search}.php` and the five `event_poll_*` tables are unchanged.
Nav
- Desktop: new entry 🗳 Event Voting in the "Events & Info" drop-down.
- Mobile: new entry 🗳 Event Voting in the drawer menu.
Cleanup
- `includes/event-popup.php` is now an empty PHP stub (kept so old includes stay valid).
- `assets/css/event-popup.css` and `assets/js/event-popup.js` are no longer referenced by any page and can be deleted in a follow-up.
29.07.2026 — WOS Upgrade Calculator (Buildings, Fire Crystal, War Academy)
New Module: `/event-hub/wos-calc.php`
- Three calculators in one page with tab navigation and trust badges per data source.
- 🏗 Buildings: pick a building, set From/To level and speedup % → see total cost (meat / wood / coal / iron / FC shards) and build time. Data covers F1–F30.
- 🔥 Fire Crystal: same form for buildings that cost fire crystals (F31–F80 + FC1–FC10 refinements). Trust badge shows 🟠 because the upstream JSON is from March 2026 and game-cost drift is possible — disclaimer in the UI explains what is reliable and what is not.
- ⚔ War Academy: pick Infantry / Lancer / Marksman, set per-skill levels and research speed → aggregated cost, time and the buff list.
Source Data
- Game data is sourced from the open-source [`whiteout-project/Whiteout-Survival-Discord-Bot`](https://github.com/whiteout-project/Whiteout-Survival-Discord-Bot) (MIT) and shipped locally as JSON under `data/wos/` so the page works without external requests.
- One independent cross-check (Fire Crystal Furnace cost) was run against `whiteoutsurvival.wiki` to confirm JSON plausibility.
UX Details
- Mushroom popup is suppressed on the calculator page via `body.wos-calc-page` CSS override.
- Mobile-friendly: same URL, responsive layout, mobile drawer link.
- New nav entries: 🧮 WOS Calculator in the desktop "Settings" drop-down and in the mobile drawer.
28.07.2026 — Profile Power-Rankings, Growth-Circle Fixes & Federation API
Top-Rankings in the Player Profile Modal
- Six new power-ranking badges now appear in the member profile modal: labyrinth, island_prosperity, total_pet_power, heros_total_power, hero_power, total_expert_power.
- The badges show rank #N · score and the top-3 ranks are highlighted in gold.
- Backed by the consumer API at `rc-c6fdf168.portal-ops.duckdns.org`, cached locally in `bot_player_rankings`, refreshed daily via a 06:00 UTC cron.
Power-Growth Circle Improvements
- The 7-day growth circle on `members.php` now aggregates power + all 6 ranking types in percent, not just power alone.
- Daily-delta logic: snapshots are aggregated per day (DATE(scanned_at) → MAX(power)) so the delta shown is always today vs. yesterday, never an accidental 4-8 day old reading.
- Sign-coloured pill (🟢 + / 🔴 −) with delta value (e.g. `+154.3M` / `-5.8M`) replaced the old threshold-class UX — much clearer at a glance.
Clickable Growth-Circle (Handover)
- The growth-circle click handler was rewritten using delegated listeners and `data-growth` attributes (the inline-onclick / short-payload approach broke silently). Hand-over document `HANDOVER-GROWTH-CIRCLE-CLICK-FIX.md` describes the failure mode and the new pattern.
Federation API Bus (Auftrag 1, LIVE)
- New Adapter / State-Profile / Bus / Container-Sync architecture lets 2180 act as the federation config-plane for customer containers.
- Admin UI: 3 fields (kid, container_id, api_token) + one button. Save = write config + `docker exec` push to the container.
- Each customer container now pulls its own data via a built-in `ConsumerApiOmarAdapter` — the data is byte-identical to the live API stream.
- Handover: `HANDOVER-2026-07-28-federation.md`.
Container Sync Improvements
- Pull-Now button in `admin/api-status.php` so admins can force a sync on demand.
- Daily cron at 06:30 UTC (`/etc/cron.d/eventhub-container-sync`) keeps the data fresh without manual interaction.
- Container auth header changed from `Authorization: Bearer` to `X-API-Key` after the upstream API was found to ignore Bearer and return `furnace_lv = 0`.
27.07.2026 — Container v7 Production-Ready, Player-DB on SQLite, Giftcode Self-Discovery
Container Image `state-hub` → v7-discovered-tested
- Image tag `v7-discovered-tested` is now the production baseline; admin pages, builder, and 9 mock codes verified end-to-end.
- Default admin credentials: `admin/admin` — please change on first login.
`playerdb.php` now on SQLite
- 4× `INSERT IGNORE` and 11× `NOW()` were rewritten for SQLite (caused silent crashes via try/catch).
- `csv_upload` and `apply` flows both got `array_merge([$fid], $vals)` so `fc` can be set reliably for new players (NAR crash gone).
- BOM in Excel-CSV: BOM-strip + positional fallback to index 0 so the `name` column is always detected.
- `bot_players.kraft` is now updated from CSV (4 Stellen precision); one-off backfill updated 629 players for State 2180.
Giftcode Module — Self-Discovery of Alliances
- The hardcoded `giftcode_alliance_state` table is gone; the giftcode page now discovers alliances via 5 distinct queries on `bot_players` — no more manual maintenance when a new alliance appears.
- PDO driver: `lib/giftcode.php` was hardcoded to MySQL ("could not find driver" in containers); now detects the container context via `STATE_NAME` env and switches to SQLite PDO automatically.
- Two missing tables (`giftcode_jobs`, `giftcode_run_log`) auto-created on first run.
Member-Frames Seeded
- `member_frame_presets` and `member_frame_rank_settings` were empty in fresh containers. Seed script with 4 presets + 2 rank-settings shipped to the `mustervorlage` image.
- R4/R5 drop-downs now show real options instead of "Disabled".
Power-Growth Tracking
- New table `player_power_history` records snapshots every 4 hours so per-player growth can be visualised.
- Threshold-coloured stat circle on `members.php` for each player: green/yellow/red/grey depending on 7-day growth percent.
State-Portal Live API
- New endpoint `/event-hub/state-portal.php` exposes the live container state to consumers.
- Authentication via `/etc/event-hub/consumer-api.token`; no cache; all changes written through with an audit log in `state_portal_sync_log`.
Misc Fixes
- `assign-member-frame.php` + `update-member-frame-rank-setting.php`: removed duplicate `session_name()` call (caused JSON parse-error by warning-HTML before the header).
- `lib/api/member-frames`: switched from MySQL `ON DUPLICATE KEY UPDATE` to SQLite `ON CONFLICT(player_id) DO UPDATE SET excluded.col`.
- Container DB schema: tables with only-schema (no PRIMARY KEY constraint) got a `PRIMARY KEY` UNIQUE INDEX added so `ON CONFLICT` works.
26.07.2026 — Build Your Own State (BYOS) B1 + B3 Live
Module Catalogue (`modules.json`)
- 31 named modules across categories: state-core, social, economy, events, analytics, admin.
- Each module has a stable id, version, display name, description, dependencies and an enabled flag.
- The catalogue is the single source of truth for what a custom-state container can install — no hard-coded module lists anywhere else.
Customer Portal & Order-Management
- New admin area Admin → State Containers (Toad-only) shows the catalogue and lets Toad pick a module bundle + container size for each customer order.
- Three fields in the order form: `customer display name`, `state kid (2180-9999)`, `container flavour` (basic, advanced, premium).
- After saving, an order appears in the order-management list with status draft / building / ready / live / archived.
First Customer Container Live
- The first container spin-up (`state-basic-test-1`) is up and running with the SQLite stack and reverse-proxy.
- Default admin: `admin / admin` (please change immediately).
- This container is the source for the next clone iteration of `mustervorlage` so future customers benefit from the same bug-fixes and improvements.
Nav
- New section Toad Only in the admin nav, visible only when the logged-in admin username is `Toad`. Links: State Orders · State Containers · State Profiles · Links.
25.07.2026 — PASS Butler Auto-Publish Pipeline Live
Butler S06 — Post Drafting
- S06 ("Post Drafting") now generates a complete post draft (caption + media references + brand-voice-checked text) from a single chosen topic.
- Lost-update pattern: the worker returns generated artefacts, the handler applies them to the draft store — worker never writes directly.
Butler S09 — Auto-Publish
- S09 ("Auto-Publish") promotes finished drafts to the chosen channel on a configured schedule.
- Daily / per-channel limit enforced; environment mount `M3-Env-Mount` carries the Lifecraft API key.
Real Lifecraft Posts
- Posts `5597`, `5598`, `5600` were published through the full pipeline as smoke-test traffic. They show up in the social-media analytics.
Butler Handover (Sections B-H)
- Cron-Parser, Brand-Voice store, Topic-Pool, KPI-Store, Guardrails, Channel-Health (S15), S09 Limit-Check — all live, all production-ready.
- Single handover doc covers the whole pipeline so a new session can come up to speed fast.
Operational Notes
- `run.ok` vs `run.status` — the worker return contract was tightened; the handler-side check was migrated to the new contract. Legacy workers that returned `{ok: bool}` are now wrapped in a shim.
- Lifecraft Response-Shapes — two distinct response formats (List-by-channel vs. Create-post) are handled in two separate client functions, with a `null`-on-missing-fields fallback.
24.07.2026 — Alliance Ranks & Layered Member Frames
Alliance Rank aus CSV
- Der Player-CSV-Import verarbeitet jetzt die vorhandene Spalte Rank und speichert R1 bis R5 für State-2180-Spieler.
- Unterstützte Header sind unter anderem `Rank`, `Alliance Rank` und `alliance_rank`.
- Werte `R1`–`R5` und `1`–`5` werden erkannt; eine leere Zelle entfernt den Rank.
- Ungültige Werte wie `R6` werden im Dry-Run angezeigt und überschreiben keinen bestehenden Rank.
- Eine CSV ohne Rank-Spalte lässt bereits gespeicherte Ranks unverändert.
- Nebenbei behoben: Eine `Public ID`/`player_id`-Spalte an Position 0 kann nicht mehr versehentlich als Spielername interpretiert werden.
Rank-Badges im Member-Popup
- Das anklickbare Spieler-Popup auf Desktop und Mobile zeigt jetzt ein farbiges Alliance-Rank-Badge.
- Farben: R1 grau, R2 grün, R3 blau, R4 violett und R5 rot-gold.
- Das Badge wird bei jedem Öffnen neu gesetzt, damit kein Rank des zuvor geöffneten Spielers stehen bleibt.
Automatische R4-/R5-Rahmen
- Unter Admin → Alliance & Players → Member Frames können getrennte automatische Rahmen für R4 und R5 eingestellt oder deaktiviert werden.
- Der automatische Rank-Rahmen wird direkt aus dem gespeicherten CSV-Rank abgeleitet und muss nicht pro Spieler manuell zugewiesen werden.
- Änderungen an Rank oder Mapping wirken beim nächsten Seitenaufruf sofort.
- Aktueller Stand: R4 → Ice, R5 → Magic.
Mehrere Rahmen als Schichten
- Ein Spieler kann gleichzeitig einen automatischen Rank-Rahmen und einen manuell vergebenen Spezialrahmen besitzen.
- Rank liegt als äußerer Layer, der manuelle Rahmen als innerer Layer; beide bleiben sichtbar und blockieren keine Klicks.
- `prefers-reduced-motion` stoppt die Animationen, lässt die statischen Rahmen aber sichtbar.
Admin-Layout & Live-Stand
- Layer Preview überdeckt den Button Assign Manual nicht mehr: breite Tabellen scrollen innerhalb ihres Bereichs; bis 1280 px erscheint die Vorschau unterhalb der Tabelle.
- Erfolgreich importiert: 469 Spieler mit Alliance Rank — R1: 94, R2: 146, R3: 171, R4: 53, R5: 5.
- Desktop- und Mobile-Darstellung sowie CSV-Dry-Run/Commit wurden live geprüft.
24.07.2026 — Event Video Building & Popup Video Integration
🎬 Event Video Building (new)
- Neues Admin-Modul zum Rendern von AI-Videos aus dem M3-Hailuo-Kontingent (3 Videos/Tag)
- 4 Modelle verfügbar: `MiniMax-Hailuo-2.3`, `MiniMax-Hailuo-02`, `T2V-01-Director`, `T2V-01`
- Dauer 6s (default) oder 10s, Auflösung 720P / 768P / 1080P
- Live-Polling alle 5s, fertige MP4s werden unter `/event-hub/uploads/event-videos/` abgelegt
- Theme-Boost: Optionale Stil-Erweiterung (Schnee, Eis, Allianz-Banner, Nordlicht, Game-of-Thrones-Winter-Stimmung)
- Galerie: Alle bisherigen Jobs mit Vorschau, Download, „↺ Als neuen Prompt verwenden"-Button
- Menü: Admin → Events & Info → 🎬 Video Building
🖼️ Image-Reference-Datenbank
- 38 CC-lizenzierte Whiteout-Survival-Theme-Bilder aus Wikipedia Commons
- Multi-Source-Harvester unter `/opt/wos-theme-harvester/harvester.sh` (Modi: `--wiki`, `--brave`, `--urls-file`)
- Log: `/var/log/wos-theme/harvester.log`, Bilder: `/var/lib/wos-theme/`
🎥 Event-Popup Video-Integration
- Pro Proposal kann jetzt ein Video angehängt werden
- Admin: `event-popup.php` → Round wählen → 🎬 Video-Button pro Proposal → Video-Picker-Modal
- Public: Video-Frame wird inline in der Proposal-Karte VOR dem Vote-Button angezeigt
- Klick auf ▶ Play startet Video mit Sound; Klick aufs Video = Play/Pause-Toggle
- Vote und Video-Interaktion sind komplett entkoppelt
📚 Doku-Updates
- `HANDOVER-EVENT-VIDEO-BUILDING-2026-07-24.md` (Single Source of Truth, neu)
- `HANDOVER-EVENT-POPUP-2026-07-23.md` (Video-Section ergänzt)
- `SYSTEM-OVERVIEW.md` (24.07. TL;DR ergänzt)
Changelog
July 23, 2026 — Interactive Event Popup with Mushroom Mascot
Auto-popup on homepage with admin-managed voting
- A new animated mushroom mascot now appears on the event-2180.com homepage and auto-opens once per day until the visitor has voted on every active event in the current round.
- After voting, the small mushroom stays as a side-launcher so the popup can be re-opened manually. The big popup auto-opens again when the next voting phase (e.g. date poll) starts.
- 50 yes-votes per proposal are required before the round advances to the date-poll phase (threshold configurable in the admin).
Admin: Event Popup manager
- New admin page `/event-hub/admin/event-popup.php` (added to the navigation under "Events & Info") for creating rounds, managing proposals and date options, switching phases, and resetting votes.
- All admin POSTs are CSRF-protected (session token via `hash_equals`).
- When a new round is created, a matching proposal with the same title is auto-inserted so the popup has content immediately.
Player search & vote flow
- Visitors search by name or FID, confirm the matching player (avatar, name, alliance, FID), and then vote once per proposal.
- Votes are immutable per (round, target, player_id) — the database unique key prevents duplicates and the API never offers an upsert/change path.
- Visitors see live tallies and a progress bar toward the 50-vote threshold; an event glows gold once reached.
- The auto-advance logic in `ep_cast_vote()` moves the round from `event_voting` to `event_closed` as soon as a proposal hits the threshold.
Mushroom mascot: lively and reactive
- The mascot is a single inline SVG (cap, stem, real eyes with pupils, blush, smile, arms, legs, ground shadow) so CSS animations can target every part.
- Behaviors: idle eye-blink, pupil darts, arm-waving, leg-twitching, soft body breathing, hopping, walking across the bottom of the screen, and a pulsing golden aura.
- Reactive behaviors (all blocked while the popup is open):
- Mouse proximity (< 140 px) → startled hop, pupils dart up, runs 60 px to the left for 1.5 s.
- 30 s of no activity → sits down, body relaxes, a "WOS TIMES" newspaper appears in its hands, animations slow down.
- Every ~30 s → giant wink: scales to 3.8×, floats to the screen center, double-winks, holds the big size for ~1.75 s, then walks back to the corner.
- All animations respect `prefers-reduced-motion`.
- ARIA labels, keyboard support (Enter/Space to open, ESC to close), and body-scroll lock while the modal is open.
Sleeping easter egg
- When no round is active the launcher falls asleep (closed eyes, slow breathing, floating "z z z").
- Three quick clicks wake it up briefly and a speech bubble appears: "No mushroom events coming up".
- Sleeping ends automatically as soon as the admin activates a new round.
Multi-round aggregation
- All active rounds with proposals are merged into a single popup response (`ep_get_active_rounds` + `ep_build_state_multi`), so visitors see every voteable event in one view, regardless of how many rounds the admin has created.
- Each proposal still routes its vote to the correct round via the JS-side `proposal.round_id` lookup.
Files & data
- New files: `lib/event_poll.php`, `api/event-popup-state.php`, `api/event-player-search.php`, `api/event-popup-vote.php`, `includes/event-popup.php`, `assets/img/mushroom.svg`, `assets/css/event-popup.css`, `assets/js/event-popup.js`, `admin/event-popup.php`.
- Modified: `index.php`, `mobile/index.php`, `mobile/includes/header.php`, `mobile/includes/footer.php`, `includes/nav.php` (new admin link).
- New tables: `event_poll_rounds`, `event_poll_proposals`, `event_poll_date_options`, `event_poll_votes`, `event_poll_admin_log`.
- Backups of all changed files use the `.bak.20260723_` / `.bak.20260724_` timestamp convention.
\n\n\n## July 22, 2026 (afternoon) — Furnace-Icon & City-Skin polish\n\n### Furnace Icon now shown everywhere\n- Previously, after Character-Sheet upload, players were shown as plain "Lv 8" without an icon, even when they were FC1–FC10.\n- Now both and render the small stove_lv_N icon (FC1–FC10) next to the label. The icon URL comes from (when it has the pattern) with a fallback to the value from Vision.\n- The character-import runner was also updated to write (not just ), so the icon appears in the members grid for every player processed via Character-Sheet upload.\n\n### City-Skin: tighter filename entropy (precautionary)\n- The Pending-City filename generator used (32-bit). Increased to (96-bit) so 1000 simultaneous uploads in the same second cannot collide.\n- Note: the duplicate-city-skin report from earlier today (Toad & Demiurge) was
not a system bug — the user uploaded the same screenshot (a shared world-map view) twice. Spreading the word: please upload a tighter crop of your own city to give each player a unique skin.\n\n## July 22, 2026 (full session) — City-Skin · Self-Service Upload · Transfer-UI · Security\n\n### Alliance City-Skin — full original\n- City-Skin images are now stored as the
full original screenshot (no crop, no downscale), so "Click to view full size" shows the whole image.\n\n### Members — Furnace ranking fix\n-
Fixed the members list sorting: it now ranks by the
actual Fire-Crystal tier (FC1-FC10 from the furnace icon), then furnace level — so FC10 players are correctly at the top, both for "All Alliances" and when filtering a single alliance.\n\n### Player Profile — Add Player via Screenshot\n-
reworked (Desktop + Mobile): the big manual form is replaced by a
screenshot upload. Players upload their Character-Sheet; Claude Vision extracts name, FID, FC, power, kills, alliance & avatar and creates/updates the profile automatically (matched by FID, so name changes are visible).\n- Two upload fields: (1) Character-Sheet screenshot (required), (2) Alliance City Skin (optional).\n- Kept manual fields: TAL Score, T11 (INF/LAN/MM), T12 (INF/LAN/MM), Contact Email, Discord — submitted together with the screenshot.\n- Validation: screenshots from a wrong state (not 2180) or non-character-sheets are rejected with a clear message.\n- Live status: "analyzing..." with auto-polling until result (accepted -> redirect, or rejected with reason).\n- Rate limit: max 3 uploads/hour/IP.\n- New files: , .\n\n### admin/playerdb.php cleanup\n-
"Image Upload" tab renamed to "Add Player" — the screenshot upload is now the way to add players.\n-
Removed the old manual
"+ Add Player" modal button and the
"Sync Now" button (WOS bot decommissioned).\n\n### admin/transfer-manage.php — Character-Sheet Upload\n- New multi-image upload section: admin uploads Character-Sheets, Vision reads name/FID/alliance, player is created in bot_players + player_db AND automatically added to
transfer_plan with status= for the current month + target_alliance from the screenshot.\n- The ? Bot-Add column and the dead Bot button are removed (WOS bot decommissioned).\n- Status column replaced with an info display:\n -
✓ Transferred (green) when the player is already in their target alliance.\n -
? Upload required (gold) when the player still needs a Character-Sheet upload (the upload box at the top of the page).\n - greyed-out
Transferred when set but alliance still drifts.\n- 1888 stale "new" transfer-requests deleted (backup kept).\n\n### SvS Player Page — Bugfix\n-
Bug: Player-Detail-Page showed "No data found" for most players.\n-
Fix: Default-Event-Pick now prefers the event with the most rows. Event 8 (newer, draft, empty) was being chosen over Event 4 (239 profiles).\n\n### Bot Crons Disabled (WOS API is dead since 21.07.)\n- Three nightly bot-related crons commented out in root-crontab:\n - (was failing anyway)\n - (was wiping all avatars)\n - (was setting the WOS default 1001.png for everyone)\n- The commented-out lines stay in crontab with an explanation for future admins.\n\n### Processing Engine\n- A
root cron now processes queued screenshot uploads every minute, because the Claude CLI is configured for root only (not www-data). Without this, the public upload would fail with "JSON parse failed" instantly.\n- Fix: handler no longer spawns the runner — it just enqueues (), root-cron does the rest as root. Fixt the admin Add-Player module too.\n\n### Security: Apache + fail2ban + recidive\n-
Apache filter : Bad-User-Agents (, , , , , , , , , , empty UA), WordPress-paths (, , , ...), path-discovery numerics (), scanner-probes (, , , , ...) →
403.\n-
fail2ban with
nftables backend, 3 jails:\n - : 200× 404 in 10 min → 1 h ban.\n - : 5× bad-bot hits in 10 min → 1 h ban.\n -
*: 5× banned within 7 d → 1 YEAR ban
(repeat offenders get permanently locked out).\n- Self-subnets (, etc.) whitelisted via .\n\n## July 22, 2026 — Self-Service Screenshot Upload + Fixes\n\n### Add Player via Screenshot (Player Profile self-service)\n- `player-profile.php` reworked
(Desktop + Mobile): the big manual form is replaced by a screenshot upload
. Players upload their Character-Sheet; Claude Vision extracts name, FID, FC, power, kills, alliance & avatar and creates/updates the profile automatically (matched by FID, so name changes are visible).\n- Two upload fields:
(1) Character-Sheet screenshot (required), (2) Alliance City Skin (optional).\n- Kept manual fields:
TAL Score, T11 (INF/LAN/MM), T12 (INF/LAN/MM), Contact Email, Discord — submitted together with the screenshot.\n- Validation:
screenshots from a wrong state (not 2180) or non-character-sheets are rejected with a clear message.\n- Live status:
the page shows "analyzing..." and auto-polls until the result (accepted -> redirect to profile, or rejected with reason).\n- Rate limit:
max 3 uploads/hour/IP (the public page triggers paid Vision analysis).\n- New files: `lib/self_service_import.php`, `api/import_status.php`. Extended: `admin/run_character_import.php` (state gate, manual-field apply, optional city image, queue mode).\n\n### Admin playerdb.php cleanup\n- "Image Upload" tab renamed to "Add Player"
— the screenshot upload is now the way to add players.\n- Removed
the old manual "+ Add Player"
modal button and the "Sync Now"
button (WOS bot decommissioned).\n\n### Alliance City Skin — full original\n- City-Skin images are now stored as the full original screenshot
(no crop, no downscale), so "Click to view full size" shows the whole image.\n\n### Members — Furnace ranking fix\n- Fixed
the members list sorting: it now ranks by the actual Fire-Crystal tier
(FC1-FC10 from the furnace icon), then furnace level — so FC10 players are correctly at the top, both for "All Alliances" and when filtering a single alliance.\n\n### Processing engine\n- A root cron
now processes queued screenshot uploads (once per minute), because the Claude CLI is configured for root only. This also powers the admin Add-Player uploads.\n\n## July 21, 2026 (evening) — Character-Image-Import + SvS-Sync\n\n### Character-Image-Import (replaces broken WOS API)\n- New Image Upload tab
in `admin/playerdb.php?filter=images` — drag-drop multi-image upload for character-sheet screenshots.\n- Vision Pipeline
in `lib/character_vision.php` + `admin/run_character_import.php` — Claude CLI extracts fid/name/fc/kraft/kills/alliance/avatar/governor-equipment (12 fields total).\n- Avatar extraction
with ImageMagick crop from screenshot's bottom-left thumbnail (small 256x256 frame near y=78-80%).\n- 2-image strategy
in Vision: full screenshot + bottom-crop pre-cut. Hard-coded fallback when Vision picks wrong area.\n- Auto-confirm
new players: status='confirmed', confirmed_by='character-import' so they appear immediately in public profile.\n- All files
: `lib/character_vision.php`, `admin/run_character_import.php`, `admin/playerdb.php`, `lib/character_vision.php`.\n\n### Profile Display Updates (4 views)\n- `player-profile.php` (Desktop) — Furnace Level, Personal Power, Personal Kills, Labyrinth now prefer SvS values when set.\n- `mobile/player-profile.php` (Mobile) — Same updates + Current-Stats-Card with avatar + all SvS values.\n- `members.php` (Popup) — Rename Kraft→Personal Power, Kills→Personal Kills, add 12 SvS-Grid.\n- `admin/playerdb.php` (Admin) — Image-Upload tab + Process-Button per row + Bulk-Process-Button.\n\n### SvS → player_db Sync (12 categories)\n- Schema:
14 new columns in `player_db` (12 svs_ values + svs_event_id + svs_last_sync_at).\n- Sync-Script
`admin/sync_svs_to_player_profile.php` — reads latest event with state_a=2180, parses appearances_json (category→value), case-insensitive player_name match with `COLLATE utf8mb4_general_ci` workaround for collation mismatch.\n- Hook
in `svs_run_ranking.php` (after main ranking block) — triggers `nohup setsid php sync_svs_to_player_profile.php $eventId` automatically.\n- Bulk-Script
`admin/bulk_create_player_db_from_svs.php` — auto-creates player_db rows for SvS players not yet in player_db (matched by name+alliance, falls back to synthetic FID 9_000_000_000+).\n - Result: 112 new synthetic profiles, 61 matched existing → 173 total synced for Event 4.\n\n### Alliance-Brackets Strip\n- Bug:
WOS UI shows `[ISS]` but bot stores `ISS` without brackets. First run stored `[ISS]` literally.\n- Fix:
`lib/character_vision.php::cleanAlliance()` strips `[ ]` and whitespace in `normalize()` for both alliance + alliance_long.\n- Validation
`admin/run_character_import.php::canonicalAlliance()` looks up case-insensitive against `bot/db/alliance.sqlite::alliance_list` and uses canonical case.\n- SQL Cleanup:
`UPDATE bot_players SET alliance_name = REPLACE(REPLACE(alliance_name, '[',''),']','');` for legacy data.\n\n### Alliance-Dropdown Cleanup\n- 24 alliance-variants for 12 real alliances (case-tiers like NAR/Nar/nAr/nar + BOT-NAR from old bot import).\n- Cleanup-Script
`admin/cleanup_alliance_duplicates.php`:\n - Case-variants merged to canonical (NAR, RUM, XYI).\n - Ghost 1-Spieler-Allianzen removed (BONK, ROK, GUD, WTF, nOb, boo, kor, Bfr).\n - Numerical Ghost 1→Friends, 17→Bye mapped via bot/alliance_list.\n - members.php dropdown filters INACTIVE/TRANSFERRED/UNKNOWN.\n- Before:
24 entries; After:
11 clean entries (Bite Your Enemies, Bye, HWS, ISS, NAR, RIV, RUM, SAK, SVS, WDG, XYI).\n\n### Sort Fix in members.php\n- Bug:
Corrupted `bp.furnace_lv` (Werte 0-80, max realistic wäre 30) lies Spieler mit FC10 ganz nach unten.\n- Fix:
New ORDER BY: `(CASE WHEN pd.svs_furnace_level IS NULL THEN 0 ELSE 1 END) DESC, pd.svs_furnace_level DESC, bp.nickname ASC`. FC10-Spieler oben, FC9 darunter, dann alphabetisch alle ohne SvS-Daten.\n\n### Click-Fix in members.php\n- Bug:
Klicks auf Spieler-Karten öffneten das Modal nicht weil `document.getElementById('p-fc7')` (nach FC7-Removal) null zurückgab → TypeError.\n- Fix:
4 tote JS-Zeilen (FC7-FC10 reference) entfernt.\n\n### FC7-FC10 Spec Cleanup\n- User-Request:
"FC7+FC8 Spec brauchts nicht mehr".\n- Fixed:
4 stat-rows aus `player-profile.php` (Desktop), `members.php` Popup, `mobile/player-profile.php` Mobile + 4 Form-Input-Felder entfernt.\n- DB-Spalten bleiben (vorsorglich).\n\n### Logs + Changelogs\n- `logs/2026-07-21-playerdb-image-upload.md` — komplette Doku aller Änderungen.\n- HANDOVER-SVS-2026-07-10.md — Update mit Phase 2/3/SvS-Sync + Alliance-Cleanup.\n- `lib/wos-alliance-brackets-strip-2026-07-21.md` — Memory-File für zukünftige Sessions.\n\n\n\n## July 21, 2026 — SQL-Injection Defense + Bugfixes\n\n### Critical Security Fix: SQL-Injection Attack Blocked\n- Attack discovered:
177 malicious entries in player_db (SQLMap UNION, time-based sleep(), XSS tests, path-traversal)\n- Timeline:
174 of 177 injected on 2026-07-17 (same day as Event-4-Delete incident - attacker exploited maintenance window)\n- Root cause:
sanitizePlayerID() regex was too permissive - allowed all alphanumeric content\n- DB cleanup:
177 malicious rows deleted (backup: player_db_malicious_backup_20260721), table now clean (530 legit rows, 0 malicious)\n\n### Security: 4-Layer Defense Implemented\n- Layer 1:
sanitizePlayerID strict regex - only numeric 6-15 digit IDs accepted, all else rejected + logged\n- Layer 2:
WAF pattern detection in player-profile.php - 11 attack patterns (union select, sleep, load_file, bcc0-, zzzScript, ../, onerror, <script) → HTTP 403 + log\n- Layer 3:
Per-IP rate limit (5 POSTs/minute) via /tmp/svs_ratelimit/ - HTTP 429 + log on excess\n- Layer 4:
Apache error.log monitoring - all BLOCKED/REJECTED events logged for analysis\n\n### UI Bugfix: members.php Duplicate Players\n- Bug:
Same player (e.g. Ares) shown 17x in members page - SQL JOIN without DISTINCT/MAX(id)\n- Cause:
player_db had 9 historical entries per FID (one per month snapshot), each joining with bot_players\n- Fix:
Changed LEFT JOIN to use subquery - now shows latest entry only\n- Result:
Ares 18x -> 1x (only legit FID is shown)\n\n### Affected URLs\n- /event-hub/members.php - duplicates fixed\n- /event-hub/player-profile.php - WAF + rate limit active\n- /event-hub/config.php - sanitizePlayerID hardened\n- /event-hub/admin/playerdb.php - unaffected (admin-only, already used prepared statements)\n\n### Backups created (for rollback)\n- members.php.bak.dupfix.20260721_125720\n- config.php.bak.sanitize-fix.20260721_130130\n- player-profile.php.bak.waf.20260721_130421\n- player_db_malicious_backup_20260721 (DB table with 177 attack rows for forensics)\n\n### Recommendations for production hardening (TODO)\n- fail2ban with custom rule for RATELIMIT BLOCKED / WAF BLOCKED patterns\n- IP-based firewall block for repeat offenders\n- Periodic DB review scripts for unusual player_id patterns\n- Rate-limit data in Redis instead of files (scales better)\n- CSP headers (additional XSS protection)\n- 2FA or captcha for POST endpoints (user-friendly)\n\n## July 19, 2026 — Discord-Bot Disk-IO Fix\n\n### Problem\n- Discord-Bot (Mushroom Maker) for Gift-Code-Redeem was failing since 2026-07-14 with 338 disk I/O errors\n- Symptoms: User gift-codes not redeemed, alliance checkups failed, manual triggers returned errors\n- All 900+ players reported not receiving gift codes\n\n### Root Cause\n- Bot had been running since 2026-07-01 (18 days uptime without restart)\n- 2026-07-13 06:30: WOS API 502 outage caused bot to enter retry loop with persistent DB connections\n- 2026-07-16 02:00-03:00: SQLite database is locked errors in journalctl\n- WAL/SHM files became stale (giftcode.sqlite-wal: 4MB, users.sqlite-wal: 0 bytes from 19.07)\n- 338 disk I/O errors in get_validation_fid() over 5 days\n\n### Fix\n- Manual bot restart via systemctl: `systemctl restart discord-bot`\n- SQLite recovery via .dump (707/749 users recovered, 42 corruption duplicates lost)\n- Corrupt users.sqlite backed up as users.sqlite.corrupt.20260719_232037\n- New users.sqlite (69632 bytes) with 707 clean rows\n\n### Verification\n- Disk I/O errors: 0 after restart\n- DB integrity check: ok\n- 84 screenshots in svs_uploads preserved\n- 239 player profiles in svs_player_profiles preserved\n\n### Lessons\n- Weekly bot restart recommended to prevent WAL/SHM lock accumulation\n- Better error handling needed for WOS API outages (timeout instead of infinite retry)\n- Consider auto-restart on disk I/O error threshold (e.g. 10 errors in 1 hour)\n\n## July 17, 2026 — Event 4 Notfall: Delete + Restore\n\n### Problem\n- Event 4 (Juli 2026, 2180 vs 2059, draft) was accidentally deleted from svs_events table by another session/instance that read outdated documentation\n- svs_uploads (146) and svs_player_profiles (239) still referenced the deleted event_id=4\n\n### Recovery\n- Event 4 re-INSERTed with original metadata (status: draft, state_a: 2180, state_b: 2059)\n- svs_rankings reconstructed from svs_player_profiles.appearances_json (12 categories x Top-50 per state = 635 players total)\n- Created at: 2026-07-12 07:28:02, admin: admin\n- Notes added to event: "Restored after accidental delete. Original data: 146 screenshots, 239 profiles, Top-Spieler Toad/Lightning/Hephaestus/Cohen/Kaelion (9 categories each), Saucy (6)"\n\n### Root Cause\n- Another Claude session / instance did not read the latest HANDOVER-SVS-2026-07-10.md before making changes\n- The svs_events table was treated as not canonical, leading to erroneous cleanup\n\n### Lessons\n- All sessions MUST read HANDOVER-.md before making any DELETE on SvS tables\n- Implement soft-delete (UPDATE status=archived) instead of hard-delete\n- Add audit log for all DELETE operations\n\n---\n\n## July 13, 2026 — Alliance Manager (Web-UI)\n\n### ? New Tab "Alliance Manager" in playerdb.php\n- Before:
Alliance transfers only via Discord-Bot (`/transfer member`).\n- Now:
Web-UI at `admin/playerdb.php?filter=alliances` — click-flow in seconds.\n- Click Flow:
Alliance → Member-Liste klappt auf → ? Move → Modal mit Alliance-Picker → Submit.\n- Alliances shown as columns
(CSS-Grid, auto-fit). 16 Alliances from `/opt/discord-bot/bot/db/alliance.sqlite` (bot's canonical source).\n- Member count + Furnace level
shown inline for each member.\n\n### 3-Way DB Sync (instant)\nEach move updates in one POST:\n- MySQL `event_hub.bot_players.alliance_name`
— drives /members.php + every website list\n- MySQL `event_hub.player_db.alliance`
— admin profile\n- Bot SQLite `users.sqlite.alliance`
(= `alliance_id` as TEXT) — bot's data stays in sync immediately\n\nPlus: Trigger-File
`/opt/discord-bot/bot/triggers/alliance_change.flag` (JSONL, append) + Audit-Log
`/tmp/alliance_changes.log` per change.\n\n### Critical Fix: PDO-SQLite busy_timeout\n- Bot process holds `users.sqlite` open with write lock → first attempt failed with `database is locked`\n- Fix: `PRAGMA busy_timeout = 5000` + `PRAGMA journal_mode = WAL` + `PDO::ATTR_TIMEOUT = 5`\n- PDO-SQLite has 0ms
busy-timeout by default — must be set explicitly\n\n### Audit + Visibility\n- Audit log `/tmp/alliance_changes.log` records every change with admin name + old/new alliance + timestamp\n- Members.php reflects changes immediately (reads from MySQL)\n- Bot reads users.sqlite on every refresh — automatically up-to-date\n\n### Also on Mobile Admin\n- Same feature in `mobile/admin/playerdb.php?filter=alliances` (mobile-optimized card layout).\n\n### ➕ Add Player to Alliance (new feature in same tab)\n- Button
"➕ Add Player to Alliance" in the alliance header (desktop) / collapsible section (mobile).\n- Modal/Form
with 2 fields: FID + Target Alliance dropdown.\n- Smart behavior
:\n - Existing FID
→ 3-way sync (same as `change_alliance`); acts as a quick transfer.\n - New FID
→ INSERT stub in all 3 DBs (`bot_players` with `nickname='(new)'`, `player_db` with `status='pending'`, bot's `users.sqlite` with `alliance_id`). Next bot sync (`sync_bot.php`) populates nickname + avatar.\n- Live lookup
via `playerdb_api.php?action=lookup_fid&fid=X` — when typing FID, shows whether player exists + current alliance (desktop only, JS fetch with 350ms debounce).\n\n## July 12, 2026 — Event 4 Live + 3 Bugfixes\n\n### SvS Event 4 (Juli 2026, 2180 vs 2059)\n- Uploads: 146 Screenshots\n- Bild-Analyse: 10/12 State A + 9/10 State B Buckets befüllt\n- Ranking: 239 Spieler aggregiert, 10 Kategorien verglichen\n- Top-Spieler: Toad (9), Lightning (9), Hephaestus (9), Cohen (9), Kaelion (9), Saucy (6)\n- Power-Sum Total: State A = 262B, State B = 280B -> State B gewinnt\n\n### Bugfixes\n- JSON-Parser (lib/svs_ocr.php): Markdown-Fences entfernt, längster Block, Fallback ohne players-Key. Behoben: 4 Buckets mit Claude-Parse-Fehlern.\n\n## July 9, 2026 — SvS Phase 4 + M3 Migration\n\n### SvS Image Upload System (24 Drop-Zones)\n- Multi-Image Upload per State+Category
— Tab "Screenshots" in the Admin with 24 Drop-Zones (12 Categories × 2 States). Drag-Drop or click, all images of a Category at once.\n- Storage:
`/uploads/svs/events/<event_id>/<state>/<category>/frame_NN_<hash>.png`\n- DB:
New table `svs_uploads` (event_id, state, category_key, frame_number, file_path)\n- Fixed:
Nested-form-Bug, Tab-Whitelist (uploads was missing), undefined $editUploadCount Variable, dir-Permissions for www-data\n\n### Claude Vision Pipeline (M3 Open-Source)\n- Switched from Claude Pro to M3
(open source via minimax.io) — NO Session-Limit anymore, can run 4-day events\n- Configuration:
`~/.bashrc.claude` + `~/.claude/settings.json` on eventz-2180\n- Removed
`.credentials.json` (Pro-Plan-Token) to force M3 endpoint\n- Async Runners:
`svs_run_bild_analyse.php` + `svs_run_ranking.php` write logs to `/tmp/svs_.log`\n\n### Player Profile with Unicode Normalization\n- name_normalized column
added to svs_player_profiles\n- Merged 12 player groups
(Saucy had 4 Unicode variants: X-Saucy-X, BUT-Sa, Saucy, ICE-X-Saucy-X-ICE)\n- svs-player.php completely rewritten
— uses svs_player_profiles (7+ entries per player instead of 1)\n- 216 player profiles
with 7-10 categories each\n\n### Bug Fixes\n- svs-ranking.php:
String+int TypeError on line 333/363 — `$i+1` → `(int)$i+1`\n- svs-player.php:
Old strcasecmp logic only found 1 of 7 Saucy entries — completely rewritten\n- svs_run_ranking.php:
ENUM did not allow "claude_multi" — ALTER TABLE\n- Upload Form:
Nested forms invalid HTML → replaced with anchor delete-links\n- Image Analysis:
import time was used without import — added import time to svs_ocr.php\n- normalizeName():
`Normalizer` class not available — replaced with `iconv("UTF-8", "ASCII//TRANSLIT//IGNORE", $name)`\n\n### German → English Translation\n- 35+ German UI strings
translated in svs-manage.php, svs-player-search.php, svs-player.php\n- 8 Category Labels
in DB: Allianz Power → Alliance Power, Erkundungsstufe → Exploration, Heldenkraft → Hero Power, etc.\n- svs_ocr.php knownCategories()
fully translated\n- Old "Original Screenshots (Fallback)" section
in svs-ranking.php disabled (wrapped in if(false))\n\n### Performance & Stats\n- 12/12 State-A buckets
filled with claude_multi data (44-54 players per bucket)\n- Top players:
Toad (10 categories, Rank #1), Lightning (10), Cohen (10)\n- Saucy:
7 categories, Rank #1 in 6 of them (true powerhouse)\n- Image analysis runtime:
~12 minutes for 11 buckets (was 5h+ with Claude Pro)\n\n## July 7, 2026\n\n### Mobile Event-Pages (event1-tundra / event2-frostdrachen / event3-minister)\n- Name-Search & Live-Suggest
— Player search accepts both Player-ID and in-game name. Backend Smart-Resolve: numeric input is used directly; names are resolved via `bot_players.nickname LIKE` (shortest match wins).\n- Live-Suggest via `/api/players.php`
— Dropdown when typing (>= 2 chars, 220ms debounce) with Avatar + Name + FID. Click on entry fills the form.\n\n\n## July 21, 2026 — WOS API Change + Discord-Bot Stopped\n\n### WOS API Change\n- Issue:
WOS has changed their API such that open names / detailed player data can no longer be fetched reliably via the old endpoints\n- Impact:
All modules depending on the WOS API are affected (gift code redemption, player sync, profile fetch)\n- Affected:
Discord-Bot Gift-Code-Redeem, scheduled sync of bot_players (furnace level, nickname, alliance), any feature pulling from wos-giftcode-api.centurygame.com\n\n### Discord-Bot Stopped\n- Status:
Stopped (systemctl stop) and Disabled (systemctl disable)\n- Reason:
WOS API no longer returns useful data; bot was returning errors on every gift-code request since 14.07.\n- Code State:
All code preserved at /opt/discord-bot/bot.backup.20260721_183639/ for future re-activation\n- DB State:
All SQLite databases backed up at /opt/discord-bot/db.backup.20260721_183639/\n- Service State:
/etc/systemd/system/discord-bot.service backed up, service disabled\n- Re-Activation Plan:
Once WOS API issue is resolved, or after /autoupdate, bot can be re-enabled (see logs/2026-07-21-wos-api-bot-stop.md for details)\n\n### New Player-Update Strategy (planned)\n- Approach:
Character-Sheet Screenshot analysis (like SvS Phase 4 Image Pipeline)\n- Workflow:
Admin uploads Character-Sheet images → Claude Vision extracts name, FID, FC, furnace level, alliance, avatar URL → UPDATE bot_players + player_db\n- UI:
New "Image Update" tab in Admin (analog to SvS Screenshots tab) - drag&drop multi-upload, background processing\n- Status:** Plan created, awaiting next session for implementation\n