This commit is contained in:
@@ -63,6 +63,31 @@
|
||||
<p>Players × observed time. One player for one hour equals one player-hour.</p>
|
||||
</details>
|
||||
</Card>
|
||||
<Card
|
||||
><Stat
|
||||
label="Matches started"
|
||||
value={metricNumber(report.matches.started)}
|
||||
size="lg"
|
||||
tween={false}
|
||||
/>
|
||||
<p>
|
||||
{report.matches.averageGroupSize === null
|
||||
? 'Average group unavailable'
|
||||
: `Average group ${metricNumber(report.matches.averageGroupSize)} ${plural(report.matches.averageGroupSize, 'player')}`}
|
||||
</p>
|
||||
<small
|
||||
>{report.matches.soloShare === null
|
||||
? 'Solo share unavailable · no recorded players'
|
||||
: `${metricNumber(report.matches.solo)} solo · ${metricPercent(report.matches.soloShare, 100)} of matches with recorded players`}</small
|
||||
>
|
||||
{#if report.matches.withRecordedPlayers < report.matches.started}<small
|
||||
>Players recorded for {metricNumber(report.matches.withRecordedPlayers)} of {countLabel(
|
||||
report.matches.started,
|
||||
'match',
|
||||
'matches'
|
||||
)}</small
|
||||
>{/if}
|
||||
</Card>
|
||||
<Card
|
||||
><Stat
|
||||
label="Time with no empty lobby"
|
||||
@@ -89,7 +114,7 @@
|
||||
<style>
|
||||
.summary {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: 16px;
|
||||
}
|
||||
p {
|
||||
@@ -117,6 +142,11 @@
|
||||
margin: 0;
|
||||
font-size: var(--text-xs);
|
||||
}
|
||||
@media (max-width: 1100px) {
|
||||
.summary {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
@media (max-width: 760px) {
|
||||
.summary {
|
||||
grid-template-columns: 1fr;
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
}: { maps: MapMetrics[]; servers: ServerMetrics[]; view?: 'maps' | 'instances' } = $props();
|
||||
const mapColumns = [
|
||||
{ key: 'map', label: 'Map' },
|
||||
{ key: 'matches', label: 'Matches' },
|
||||
{ key: 'playerHours', label: 'Player-hours' },
|
||||
{ key: 'active', label: 'With players' },
|
||||
{ key: 'noEmpty', label: 'No empty lobby' },
|
||||
@@ -103,12 +104,14 @@
|
||||
const mapPage = $derived(metricPage(mapRows, params.get('mapPage')));
|
||||
const serverPage = $derived(metricPage(serverRows, params.get('serverPage')));
|
||||
const currentPage = $derived(view === 'maps' ? mapPage : serverPage);
|
||||
const currentQuery = $derived(view === 'maps' ? mapQuery : serverQuery);
|
||||
const pageKey = $derived(view === 'maps' ? 'mapPage' : 'serverPage');
|
||||
function gameName(game: string) {
|
||||
return game === 't5' ? 'Black Ops I' : game === 't6' ? 'Black Ops II' : game.toUpperCase();
|
||||
}
|
||||
function mapValue(map: MapMetrics, key: Exclude<MapSort, 'map'>): number | null {
|
||||
if (key === 'details') return null;
|
||||
if (key === 'matches') return map.matches.started;
|
||||
if (key === 'noEmpty')
|
||||
return map.availabilityObserved ? map.noEmpty / map.availabilityObserved : null;
|
||||
return (key === 'playerHours' || key === 'active') && !map.observed ? null : map[key];
|
||||
@@ -233,6 +236,13 @@
|
||||
: ''}</small
|
||||
></th
|
||||
>
|
||||
<td
|
||||
>{metricNumber(map.matches.started)}<small
|
||||
>{map.matches.averageGroupSize === null
|
||||
? 'Average group unavailable'
|
||||
: `avg group ${metricNumber(map.matches.averageGroupSize)}`}</small
|
||||
></td
|
||||
>
|
||||
<td class="player-hours"
|
||||
>{map.observed ? metricNumber(map.playerHours) : '—'}
|
||||
<div class="comparison" aria-hidden="true">
|
||||
@@ -249,6 +259,18 @@
|
||||
><span class="sr-only">{map.map} </span>Details</summary
|
||||
>
|
||||
<dl>
|
||||
<dt>Matches with recorded players</dt>
|
||||
<dd>
|
||||
{metricNumber(map.matches.withRecordedPlayers)} of {metricNumber(
|
||||
map.matches.started
|
||||
)}
|
||||
</dd>
|
||||
<dt>Solo matches</dt>
|
||||
<dd>
|
||||
{map.matches.soloShare === null
|
||||
? 'Unavailable · no recorded players'
|
||||
: `${metricNumber(map.matches.solo)} · ${metricPercent(map.matches.soloShare, 100)} of matches with recorded players`}
|
||||
</dd>
|
||||
<dt>Observed lobby time</dt>
|
||||
<dd>{metricDuration(map.observed)}</dd>
|
||||
<dt>Unknown status</dt>
|
||||
@@ -308,12 +330,19 @@
|
||||
</table>
|
||||
</div>
|
||||
{:else}<EmptyState
|
||||
title={view === 'maps' ? 'No maps match this search' : 'No instances match this search'}
|
||||
>{#snippet actions()}<Button
|
||||
size="sm"
|
||||
onclick={() => updateContext({ [view === 'maps' ? 'mapQ' : 'serverQ']: '' })}
|
||||
>Clear search</Button
|
||||
>{/snippet}</EmptyState
|
||||
title={currentQuery.trim()
|
||||
? view === 'maps'
|
||||
? 'No maps match this search'
|
||||
: 'No instances match this search'
|
||||
: view === 'maps'
|
||||
? 'No map history in this window'
|
||||
: 'No instance observations in this window'}
|
||||
>{#snippet actions()}{#if currentQuery.trim()}<Button
|
||||
size="sm"
|
||||
onclick={() =>
|
||||
updateContext({ [view === 'maps' ? 'mapQ' : 'serverQ']: '' })}
|
||||
>Clear search</Button
|
||||
>{/if}{/snippet}</EmptyState
|
||||
>{/if}
|
||||
<div class="footer">
|
||||
<span
|
||||
@@ -342,7 +371,7 @@
|
||||
<summary>About these metrics</summary>
|
||||
<p>
|
||||
{view === 'maps'
|
||||
? 'No empty lobby measures map time when at least one tracked lobby has known occupancy and none report zero players. If every lobby is unknown, map availability is unknown. Player limits are not required. This does not measure failed joins or unmet demand. A dash means unavailable, not zero.'
|
||||
? 'Matches counts retained matches started in this window, including ongoing and interrupted matches. Average group counts distinct recorded players across each match’s lifetime and excludes matches with no recorded players. No empty lobby measures map time when at least one tracked lobby has known occupancy and none report zero players. If every lobby is unknown, map availability is unknown. Player limits are not required. This does not measure failed joins or unmet demand. A dash means unavailable, not zero.'
|
||||
: 'Time with players excludes unknown status and collection gaps. Rows group historical observations by name and may span multiple registrations. Links open the current instance with that name.'}
|
||||
</p>
|
||||
</details>
|
||||
|
||||
Reference in New Issue
Block a user