docs: plan player achievements and visual refresh
This commit is contained in:
@@ -33,6 +33,10 @@ and launch your first server.
|
||||
|
||||
## Plans
|
||||
|
||||
[Player achievements and visual refresh](plans/player-achievements-and-visual-refresh-plan.md)
|
||||
proposes calendar leaderboards, personal-best highlights, shareable result cards,
|
||||
and consistent map and status presentation.
|
||||
|
||||
[Lobby autoscaling](plans/lobby-autoscaling-plan.md) records the implemented design
|
||||
and the production dry run, pilot measurements, and rollout still to complete.
|
||||
Current operating instructions live in the guides above.
|
||||
|
||||
@@ -0,0 +1,296 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user