feedback7: merge fb7-stats — the local statistics page (PR #323 by @2321Robin, reworked): a match counts when it reached its result or the player cleared at least one round before quitting; earlier quits stay in the history marked 中途退出 / 不计入统计 (the owner's rule of 2026-10-08)

- Conflict: docs/PLAYING.md — #321's §12 反馈问题 and the stats §12 both added; the stats section is §13 (README anchor
  updated).
- Golden unchanged; broad node --test 5382 (5381 pass, 0 fail, 1 skipped); lint 0 errors.
This commit is contained in:
sganggs committed 2026-10-08 20:45:05 +08:00
commit e183f2fabd
21 files changed
+2691 -11

No files matched your search

+11 -1
View File
@@ -49,6 +49,8 @@ import { settingsStore } from './ui/settings.js';
import { GuideHost } from './ui/guide.js';
import { installDeviceSupport } from './ui/device.js';
import { LoadoutHost } from './screens/loadout.js';
import { StatsHost } from './screens/stats.js';
import { recordResult, installStatsRecorder } from './ui/stats.js';
import { installLoadoutSync, installOwnershipSync, installDiySync } from './ui/loadoutSync.js';
import { startBuildGuard } from './ui/buildGuard.js';
import { initLang, useLang, tickerText } from './ui/lang.js';
@@ -220,7 +222,13 @@ function wireNet() {
net.on('m.public', (msg) => { matchAt = Date.now(); store.patch('match', { public: payload(msg) }); maybeFinishRestore(); });
net.on('m.private', (msg) => { matchAt = Date.now(); store.patch('match', { private: payload(msg) }); });
net.on('m.field', (msg) => store.patch('match', { field: payload(msg) }));
net.on('m.result', (msg) => store.patch('match', { result: payload(msg) }));
net.on('m.result', (msg) => {
const res = payload(msg);
store.patch('match', { result: res });
// 本机统计 (PR #323): every arrival, including the lobby's result replay after a reconnect / reload —
// replays dedupe by content id inside recordResult (spectator seats' copies build no record at all)
recordResult(res, { myId: store.get().me.playerId, roomMode: store.get().room?.mode ?? null, now: Date.now() });
});
net.on('m.toast', (msg) => {
const kind = ['info', 'success', 'warn', 'error'].includes(msg.kind) ? msg.kind : 'info';
// msgid + params (server ≥ 0.2.0) or the text itself as a msgid, in the current language
@@ -288,6 +296,7 @@ function App() {
<${UiHosts} />
<${GuideHost} />
<${LoadoutHost} />
<${StatsHost} />
</div>`;
}
@@ -342,6 +351,7 @@ async function boot() {
}));
wireNet();
installStatsRecorder(store); // follows the match on screen, so a 放弃模拟 can be recorded (ui/stats.js)
installLoadoutSync({ net });
installOwnershipSync({ net });
installDiySync({ net });