Files
electric-cherry/docs/plans/player-achievements-and-visual-refresh-plan.md
T

297 lines
19 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.
# Player achievements and visual refresh implementation plan
Status: Proposed October 7, 2026. Implementation has not started.
Make Electric Cherry more rewarding to revisit by showing recent player
achievements, making match results easy to share, and giving maps a recognizable
visual identity. Deliver player achievements first, then refresh the presentation
across Fleet, Autoscaling, and operations. Each workstream should be useful on
its own.
## Intended experience
- A player can compare this week's or this month's results, open the match that
earned a record, and see how it improved on their previous best.
- An operator can export a readable match or player result card without manually
assembling a screenshot.
- Maps and games are recognizable at a glance. Player counts, rounds, health,
and required actions have a clear visual order.
- Routine states look calm. Failures, stale observations, and safety blockers
remain visible without opening technical details.
## Scope and defaults
| Area | First release |
| --- | --- |
| Leaderboard periods | All time, This week, and This month, with previous/next period navigation. Keep All time as the default. |
| Calendar | Weeks start Monday at 00:00 UTC; months start on the first at 00:00 UTC. Show the boundaries explicitly. Personal timezone preferences format timestamps without changing competition membership. |
| Personal bests | Highest round, best game kills, and highest reported score, compared within the same game and map variant. |
| Sharing | A preview and PNG download for a match summary or one player's result, plus an authenticated match link. Native device sharing is an optional enhancement with download/copy fallbacks. |
| Visual direction | Existing dark surfaces, electric-blue accent, typography, and brand mark, extended with original SVG map emblems and consistent game/status treatments. |
| Initial visual coverage | Fleet, server detail, Autoscaling, provisioning and host operation history, player highlights, and match/share cards. |
Season rewards, player-account linking, public profile/match pages, notification
subscriptions, rank-change tracking, custom themes, and photographic map artwork
are outside this release. These features need separate product decisions.
## Existing foundations
The [leaderboard guide](../guides/leaderboard.md) defines the current qualification
rules, player identity, record evidence, and access model. Completed matches and
player participation persist independently of live servers and operational
history retention. Reuse these records rather than requiring reporter changes.
The leaderboard already has game/map/metric filters, shareable filter URLs,
player profiles, source-match links, and refresh behavior that protects someone
inspecting a row. Fleet already has search, pins, grouping, cards/table views,
and a live match spotlight. Preserve those behaviors.
Extend the [shared UI patterns](../development/ui-patterns.md), semantic tokens,
`ServerIdentity`, `StatusBadge`, and existing disclosure controls. The visual
work should refine their use across pages.
## Player achievements
### Calendar rankings
Add validated `period=all|week|month` and optional `start=YYYY-MM-DD` query
parameters. An omitted start selects the current period; an explicit start must
be a Monday for a week or the first day for a month. Reject malformed dates,
misaligned boundaries, and a start supplied with All time. Disable navigation
into future periods. Existing URLs without these parameters retain their behavior.
Return resolved period metadata, including inclusive start, exclusive end, and
the fixed UTC timezone. Use the same resolved boundaries for rankings, summary
totals, eligibility thresholds, exact-player rank, selected profile history,
and record evidence. Apply a period to a match's completion time, so a match
crossing midnight belongs entirely to the period in which it completed.
Retain the existing rules: only completed games reaching round 2 with qualifying
player participation contribute; ties share rank; search preserves actual rank;
GUIDs combine across T5/T6; map variants remain distinct. Efficiency thresholds
apply to matches inside the selected period. Keep known map options available
when a selected period has no results.
On the profile, label lifetime totals separately from the selected competition
and filtered match history. Carry period/start through profile links, record
links, pagination, and return navigation. Reset pagination when the period
changes. A copy-link action should resolve the current period to an explicit
start so the shared link still identifies the same competition next week.
Add an additive nullable `first_completed_at` field to historical matches. Set it
once when a match first becomes completed. The current ingestion path can update
`ended_at` when a changed final report arrives; a correction must not move a
match into another competition. Preserve the existing `ended_at` behavior.
Backfill completed rows from valid `ended_at` values as the best available
historical estimate. Unknown completion times remain eligible for All time but
are excluded from dated competitions, with that limitation explained in report
help.
Create any index on the new field after the additive column migration. Startup
currently executes the base schema before adding columns to existing databases.
Use a dedicated, idempotent migration that normalizes valid values to ISO UTC;
existing timestamp-normalization markers must not cause upgrades to skip it.
Evaluate a partial completion-time index for completed matches using query plans
and the existing synthetic benchmark before selecting the final index. Period
membership stays stable after correction, while corrected counters can update
the rankings for a past period.
Resolve the period before report submission, and include its bounds in all
in-flight, worker-response, and ranked-scope cache keys. A week or month must
roll over without requiring a database write. Extend evidence requests with the
same bounds so a period's record never links to an equal result from another
period. Preserve the current all-time evidence tie-breaking behavior.
Primary changes:
- [Leaderboard types](../../src/lib/leaderboard.ts), API validation, page and
profile loaders, and [navigation context](../../src/lib/leaderboard-navigation.ts).
- [Ranking queries](../../src/lib/server/fleet/leaderboard.ts),
[record evidence](../../src/lib/server/fleet/leaderboard-records.ts), player
profile queries, and report worker/cache inputs.
- Database schema/migration and
[completion ingestion](../../src/lib/server/fleet/leaderboard-ingest.ts).
### Personal best highlights
Show a compact Recent personal bests section on player profiles and annotate
the relevant results on completed match pages. Use copy such as “Highest round
on Town Survival: 31, up from 27.” Identify whose record it is; dashboard accounts
are not currently linked to player GUIDs.
A milestone is a strict improvement over all earlier qualifying results for the
same GUID, game, map key, and metric. Order by `first_completed_at`, then match ID
for deterministic simultaneous completions. An equal result is not a new record.
A first qualifying result is labeled First recorded result rather than an
improvement. A later record does not remove the earlier match's “Personal best
at the time” annotation. Current record evidence remains a separate concept.
Derive milestones in the report worker from retained history for the requested
player or bounded match roster. Compare against the complete earlier history
before applying the displayed date range or result limit. Do not make the first
match of each week a false personal best by limiting the comparison to that week.
Batch the requested player/map scopes to avoid one query per metric or row.
Use undated historical values as a conservative comparison floor and do not
emit dated milestones for them. Explain in methodology that incomplete older
timestamps can suppress a highlight. Recompute derived milestones after counter
corrections using the existing report revision invalidation. Duplicate reports
must not create duplicate highlights. No new event journal, notification history,
or full-history startup backfill is needed.
Initially show at most five grouped match highlights on a profile, with a link
to its existing match history. Follow the selected game/map and competition
period for displayed highlights while comparing against all earlier history.
Group multiple improved metrics from one match.
Keep the existing qualification caveats near the evidence: highest round can
include late joins, score is the highest observed balance, and first observation
does not prove continuous participation. Add no confetti or repeating animation.
Add a dedicated milestone query/presentation module alongside the existing
leaderboard records and pass only the needed results to profile and match pages.
Use explicit report-worker job handling for these reads rather than expanding
the worker's current player-report fallback implicitly.
### Shareable match results
Add Share result to completed match pages, and a player-specific action to each
eligible result. Open an accessible dialog with two layouts: Match summary and
Player result. The preview and exported image use the same bounded presentation
model.
The summary includes map, game, final round, completion date, and known duration.
The player layout adds display name, recorded player statistics, and applicable
personal-best improvements. Use the player's observed highest round for their
result, which may differ from the match's final round. Include relevant late-join
or missing-start context. Omit unknown values rather than rendering them as zero.
Render a 1200 × 630 PNG locally using bundled fonts and assets. Share-card layout
code must wrap or deliberately truncate long names without dropping essential
qualifications. Provide semantic HTML for the preview; the canvas is an export
implementation detail. Wait for fonts/assets before export and show a retryable
error if generation fails. Native sharing, when supported, must run only from an
explicit user action.
Construct an explicit export model. Exclude chat, GUIDs, internal instance IDs,
addresses, keys, and raw diagnostics; the current match detail object includes
chat and must not be exported wholesale. Offer a plain match link labeled as
requiring Electric Cherry access. Do not change route permissions or introduce
an unauthenticated image endpoint. Downloads remain useful to recipients without
a dashboard account.
Implement the first version with the current brand styles. Apply the new map
emblems during the visual workstream without changing result semantics.
## Visual refresh
### Map and game identity
Add a presentation resolver keyed by game and selected map slug, backed by the
existing [map catalog](../../src/lib/map-aliases.ts). Use original small SVG
emblems: a theater marquee for Kino, a rocket for Ascension, a moon for Moon,
prison bars for Mob, and excavation/gears for Origins. Complete the catalog with
the same simple line or silhouette style and a neutral custom-map fallback.
Maps sharing an environment may share artwork, while Survival, Grief, Turned,
and other variants retain explicit names. Historical views resolve identity from
their stored game/map key and variant, never from a replacement live server.
An ambiguous engine map must use a fallback rather than inventing a variant.
Extend `MatchDetail`, `PlayerMatch`, and their queries with the stored map key;
they currently expose friendly map names without that identity. Do not infer
historical variants from display names.
Introduce `MapEmblem` and a shared game label/badge. Keep Black Ops I/II readable
without color, using distinct I/II marks where space is limited. Put larger
emblems on map headings and result cards, smaller ones on lobby cards, and keep
table mode compact. Artwork is decorative when adjacent text names the map.
Use locally shipped, dimensioned SVG assets or components. Keep the current
fonts and dark/electric-blue palette. Game decoration must not reuse danger or
warning tones. Add only the semantic tokens needed for identity and achievement
styling, and document their use.
### Information order and status consistency
| Surface | Presentation changes |
| --- | --- |
| Fleet cards | Map/lobby/game first; health, player count, and round next; roster and match duration below; lower-priority timing and technical identity in supporting details. Preserve compact empty cards, search, pins, table mode, and the match spotlight. |
| Server detail | Add map/game identity to the shared header with health. Keep player counts, round, and timing in the Live content so Configuration and Diagnostics remain focused on their task. |
| Autoscaling | Use shared identity and mode badges, a compact count strip, and a prominent current decision/blocker. Keep per-map targets, Dry run/live mode, ownership, global pause, and unsettled operations visible. Reuse existing evidence/history disclosures. |
| Provisioning and host operations | Use consistent labels and status components while preserving action, target, result, elapsed time, and specific wait reasons. Put long IDs, source paths, and low-level metadata behind explicit details/copy controls. |
| Player and match results | Reuse map identity and achievement treatment across profile highlights, match results, and downloaded cards. Distinguish a current record from a record earned at that time. |
Align status presentation by meaning, while preserving domain-specific labels.
For example, `StatusPip` currently colors starting/provisioning amber while shared
badges use an informational treatment. Normal queued work and waiting for players
should not look like failures. Keep the specialized provisioning status helpers
that explain why an operation is waiting.
Do not collapse freshness warnings, unknown player counts, failed operations,
safety blockers, pending destructive work, or unsettled commands. Raw identities
must remain reachable through keyboard-accessible details and copy controls.
Preserve navigation context, permission checks, confirmations, and existing
polling/store behavior.
## Delivery sequence
Complete, validate, and commit each phase before starting the next. Finish the
player-achievement workstream before starting the visual refresh. Commit any
unrelated fix separately.
| Phase | Reviewable result | Acceptance gate |
| --- | --- | --- |
| A1 Completion time and period queries | Stable completion field/migration, period parsing, scoped ranking/evidence/profile queries, and cache keys. | Boundary, correction, qualification, and all-time compatibility tests pass; query plans and representative timing are recorded. |
| A2 Period navigation | Week/month controls, history navigation, period metadata, profile context, and stable share URLs. | Direct links, back/forward, rollover, empty periods, and inspection-preserving refresh work on desktop and mobile. |
| A3 Personal best highlights | Derived milestones, profile section, and match annotations. | Strict improvements, ties, first results, variants, corrections, and undated history behave as specified; long player histories remain practical. |
| A4 Result sharing | Accessible preview, PNG download, authenticated copy-link action, and supported native sharing. | Export matches the preview, contains only selected result data, handles long/incomplete data, and works after live-server deletion. Update leaderboard documentation and commit the completed workstream. |
| B1 Shared visual identity | Original emblems, resolver, game labels, achievement tokens, and usage guidance. | Every map selection resolves correctly or falls back; no misleading variant, color-only meaning, or missing-asset layout shift. |
| B2 Fleet and operational presentation | Apply identity/hierarchy/status rules to Fleet, server detail, Autoscaling, provisioning, and host operation history. | Existing actions and critical states remain visible and usable; no new polling or behavior changes. |
| B3 Player presentation and review | Apply the shared visual identity to player highlights, match pages, and export layouts; refresh documentation images. | Complete the visual/accessibility review and update UI guidance, then commit the completed workstream. |
## Validation
Use an isolated synthetic database. Extend existing fixture helpers; the current
`seed:fixture` package script points to a missing script and must not be assumed
to provide a working preview dataset.
For achievements, cover Monday/month/year boundaries, leap years, viewer timezone
differences, cross-boundary matches, unknown completion timestamps, and corrections
after completion. Test upgrade from an existing database, timestamp normalization,
and restart after the completion migration. Verify that an idle database still
changes the current period at rollover. Test period-scoped record sources, ties, efficiency eligibility,
GUID identity across games, T6 variants, deleted servers, profile pagination,
and all-time response compatibility. Match/player qualification must be shared
with existing leaderboard logic.
Test personal-best comparison against full prior history, simultaneous completion
ordering, duplicate final reports, first results, later-surpassed records, and
grouping several improvements from one match. Benchmark a player with a long
history and a full roster. Bound request scopes and response sizes; do not hide
query costs by silently ignoring older matches. Keep report work off the main
request thread. Extend the existing leaderboard benchmark for period queries
and record before/after cold and warm timings under the same fixture.
For sharing, verify the explicit export model, missing values, long/Unicode
names, font/asset readiness, downloaded-image readability, and browser fallbacks.
Confirm that anonymous access to match/profile pages remains unchanged and that
sharing requires an explicit action.
Review representative screens at 1440, 390, and 320 pixels, with 200% zoom,
keyboard navigation, reduced motion, and long custom names. Include empty,
occupied, stale, offline, starting, failed, waiting, and paused states. Check
contrast, focus order, non-color status labels, readable export text, and absence
of nested interactive targets. Capture before/after screenshots using identical
fixtures; verify that map, players/round, health, and required action are easy to
identify without opening details.
During implementation, run `npm run check`, focused Vitest tests for changed
behavior, and `npm run build` for each relevant phase. Before completing each
workstream, run the full controller test suite and browser review. Agent tests
are needed only if agent code or its protocol changes; neither is planned here.
Use meaningful behavior/query tests for new rules and manual visual checks for
decorative changes.
After delivery, fold lasting behavior into the leaderboard guide and UI patterns,
refresh the Fleet documentation image, and remove this completed plan according
to the repository documentation convention.