Add capture-presentation layer (cursor pre-glow + ghost-card arc + caption)
Captured trailer footage was technically correct but visually unclear —
Captured trailer footage was technically correct but visually unclear — cards just disappeared from the hand and a blue streak hit the enemy with damage numbers. Viewer couldn’t tell which card was being played or what it did.
New AIApocalypse.Demo.CapturePresentation MonoBehaviour layers three trailer-grade visual beats over each card play during sequence captures:
-
Ghost cursor pre-glow (0.6s before play) A synthetic cursor sprite slides in from off-screen and lands on the card with a yellow glow that pulses on the final beat. Sells the “the player chose this card” moment.
-
Ghost card arc (0.55s during play) A simplified copy of the card animates along a quadratic bezier from the hand position to the target enemy (or player portrait for self-target). Scales 1.0 -> 0.4 + fades during travel. Card is tinted by type (Attack=red, Skill=blue, Power=magenta) and labeled with the card name in big bold caps.
-
Caption overlay (1.5s, fade in/out) Bottom-third black bar with “CARD NAME · EFFECT SUMMARY” text. SummarizeEffect picks the most-prominent stat (“DEAL 19 DAMAGE”, “GAIN 12 ENERGY SHIELD”, “DEAL DAMAGE = ARMOR” for Shield Bash, “DEAL DAMAGE = 2× SHIELD” for Psionic Slam).
Gated by DemoFeatureFlags.ShowCapturePresentation = true ONLY in Claude demo mode (footage capture). Real game / Player Demo are untouched.
ScriptedInputProvider.FirePlayCard now finds the CardUI (not just CardData) so the presentation layer can position visuals on the specific card. Pre-presentation delay (~0.6s) defers PlayCard via DelayedPlayWithPresentation coroutine.
PopulateHeroSequences: bumped scripted-input timestamps to 2s spacing between card plays (was 1s). Sequence durationSeconds bumped to 14s/16s/13s (was 8s) so each presentation has time to land. Markers updated to match new killing-blow timing.
Telemetry — splits broken overview SQL into N independent count
queries combined client-side (Promise.all). Each part is plain
SELECT count() FROM ... WHERE ... — no fragile aggregate
combinators. Middleware 401 returns JSON instead of plain text so
the dashboard’s r.json() never chokes on auth failures. Helper
functions runAeQuery / runAeQueryRaw / extractScalar factored out.
Co-Authored-By: Claude Opus 4.7 (1M context) [email protected]
Commit 55ae493 by astafford8488.