@rine-kit/pubg-intelligence 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/LICENSE +21 -0
  2. package/dist/src/analytics.d.ts +176 -0
  3. package/dist/src/analytics.js +431 -0
  4. package/dist/src/analytics.js.map +1 -0
  5. package/dist/src/assistant.d.ts +87 -0
  6. package/dist/src/assistant.js +310 -0
  7. package/dist/src/assistant.js.map +1 -0
  8. package/dist/src/battle.d.ts +280 -0
  9. package/dist/src/battle.js +1249 -0
  10. package/dist/src/battle.js.map +1 -0
  11. package/dist/src/combat-long.d.ts +42 -0
  12. package/dist/src/combat-long.js +146 -0
  13. package/dist/src/combat-long.js.map +1 -0
  14. package/dist/src/combat-quality.d.ts +101 -0
  15. package/dist/src/combat-quality.js +253 -0
  16. package/dist/src/combat-quality.js.map +1 -0
  17. package/dist/src/evaluation-fixtures.d.ts +13 -0
  18. package/dist/src/evaluation-fixtures.js +144 -0
  19. package/dist/src/evaluation-fixtures.js.map +1 -0
  20. package/dist/src/evaluation-replay.d.ts +5 -0
  21. package/dist/src/evaluation-replay.js +107 -0
  22. package/dist/src/evaluation-replay.js.map +1 -0
  23. package/dist/src/evaluation.d.ts +140 -0
  24. package/dist/src/evaluation.js +180 -0
  25. package/dist/src/evaluation.js.map +1 -0
  26. package/dist/src/experiment-store.d.ts +56 -0
  27. package/dist/src/experiment-store.js +159 -0
  28. package/dist/src/experiment-store.js.map +1 -0
  29. package/dist/src/field.d.ts +103 -0
  30. package/dist/src/field.js +298 -0
  31. package/dist/src/field.js.map +1 -0
  32. package/dist/src/fights.d.ts +89 -0
  33. package/dist/src/fights.js +238 -0
  34. package/dist/src/fights.js.map +1 -0
  35. package/dist/src/highlights.d.ts +44 -0
  36. package/dist/src/highlights.js +153 -0
  37. package/dist/src/highlights.js.map +1 -0
  38. package/dist/src/index.d.ts +24 -0
  39. package/dist/src/index.js +25 -0
  40. package/dist/src/index.js.map +1 -0
  41. package/dist/src/insights.d.ts +44 -0
  42. package/dist/src/insights.js +343 -0
  43. package/dist/src/insights.js.map +1 -0
  44. package/dist/src/intent.d.ts +40 -0
  45. package/dist/src/intent.js +119 -0
  46. package/dist/src/intent.js.map +1 -0
  47. package/dist/src/landing.d.ts +74 -0
  48. package/dist/src/landing.js +195 -0
  49. package/dist/src/landing.js.map +1 -0
  50. package/dist/src/lobby.d.ts +48 -0
  51. package/dist/src/lobby.js +209 -0
  52. package/dist/src/lobby.js.map +1 -0
  53. package/dist/src/match-insight.d.ts +165 -0
  54. package/dist/src/match-insight.js +542 -0
  55. package/dist/src/match-insight.js.map +1 -0
  56. package/dist/src/match-relative.d.ts +73 -0
  57. package/dist/src/match-relative.js +145 -0
  58. package/dist/src/match-relative.js.map +1 -0
  59. package/dist/src/match-story.d.ts +155 -0
  60. package/dist/src/match-story.js +636 -0
  61. package/dist/src/match-story.js.map +1 -0
  62. package/dist/src/profiles.d.ts +41 -0
  63. package/dist/src/profiles.js +136 -0
  64. package/dist/src/profiles.js.map +1 -0
  65. package/dist/src/raw-telemetry-archive.d.ts +29 -0
  66. package/dist/src/raw-telemetry-archive.js +340 -0
  67. package/dist/src/raw-telemetry-archive.js.map +1 -0
  68. package/dist/src/raw-telemetry-backfill.d.ts +28 -0
  69. package/dist/src/raw-telemetry-backfill.js +144 -0
  70. package/dist/src/raw-telemetry-backfill.js.map +1 -0
  71. package/dist/src/research.d.ts +89 -0
  72. package/dist/src/research.js +376 -0
  73. package/dist/src/research.js.map +1 -0
  74. package/dist/src/rhythm.d.ts +104 -0
  75. package/dist/src/rhythm.js +260 -0
  76. package/dist/src/rhythm.js.map +1 -0
  77. package/dist/src/streaks.d.ts +117 -0
  78. package/dist/src/streaks.js +367 -0
  79. package/dist/src/streaks.js.map +1 -0
  80. package/dist/src/style.d.ts +304 -0
  81. package/dist/src/style.js +1461 -0
  82. package/dist/src/style.js.map +1 -0
  83. package/dist/src/tactics.d.ts +88 -0
  84. package/dist/src/tactics.js +1221 -0
  85. package/dist/src/tactics.js.map +1 -0
  86. package/package.json +49 -3
  87. package/README.md +0 -3
@@ -0,0 +1,195 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { parseTelemetryPayload, } from '@rine-kit/pubg-domain';
3
+ import { mapDisplayName, mapSizeKm, median, newcombeDifference, wilsonInterval, } from './analytics.js';
4
+ import { CONTEST_MIN_MATCHES } from './field.js';
5
+ export const LANDING_SCHEMA_VERSION = 1;
6
+ export const LANDING_EARLY_WIPE_SECONDS = 420;
7
+ export const LANDING_CELL_KM = 1;
8
+ export function landingPlayerRef(accountId) {
9
+ return createHash('sha256').update(accountId).digest('hex').slice(0, 12);
10
+ }
11
+ function objectValue(value) {
12
+ return value && typeof value === 'object' && !Array.isArray(value)
13
+ ? value
14
+ : {};
15
+ }
16
+ function finite(value) {
17
+ return typeof value === 'number' && Number.isFinite(value) ? value : null;
18
+ }
19
+ export function gridCell(xMeters, yMeters, mapName) {
20
+ const size = mapSizeKm(mapName);
21
+ if (size === null)
22
+ return null;
23
+ const col = Math.floor(xMeters / 1000 / LANDING_CELL_KM);
24
+ const row = Math.floor(yMeters / 1000 / LANDING_CELL_KM);
25
+ if (col < 0 || row < 0 || col >= size || row >= size)
26
+ return null;
27
+ return `${String.fromCharCode(65 + col)}${row + 1}`;
28
+ }
29
+ /** Reads LogParachuteLanding for the tracked squad; coordinates in telemetry are centimetres. */
30
+ export function extractLandings(payload, match, memberIds) {
31
+ const seen = new Set();
32
+ const landings = [];
33
+ for (const raw of parseTelemetryPayload(payload)) {
34
+ const event = objectValue(raw);
35
+ if (event._T !== 'LogParachuteLanding')
36
+ continue;
37
+ const character = objectValue(event.character);
38
+ const accountId = typeof character.accountId === 'string' ? character.accountId : null;
39
+ if (!accountId || !memberIds.has(accountId) || seen.has(accountId))
40
+ continue;
41
+ const location = objectValue(character.location);
42
+ const x = finite(location.x);
43
+ const y = finite(location.y);
44
+ if (x === null || y === null)
45
+ continue;
46
+ seen.add(accountId);
47
+ const xMeters = x / 100;
48
+ const yMeters = y / 100;
49
+ landings.push({
50
+ playerRef: landingPlayerRef(accountId),
51
+ xMeters,
52
+ yMeters,
53
+ cell: gridCell(xMeters, yMeters, match.mapName),
54
+ });
55
+ }
56
+ return landings;
57
+ }
58
+ function modalCell(landings) {
59
+ const counts = new Map();
60
+ for (const landing of landings)
61
+ if (landing.cell)
62
+ counts.set(landing.cell, (counts.get(landing.cell) ?? 0) + 1);
63
+ let best = null;
64
+ let bestCount = 0;
65
+ for (const [cell, count] of [...counts.entries()].sort(([left], [right]) => left.localeCompare(right))) {
66
+ if (count > bestCount) {
67
+ best = cell;
68
+ bestCount = count;
69
+ }
70
+ }
71
+ return best;
72
+ }
73
+ function placementOf(players) {
74
+ const ranks = new Set(players
75
+ .map((player) => player.rank)
76
+ .filter((rank) => rank !== null && rank > 0));
77
+ return ranks.size === 1 ? [...ranks][0] : null;
78
+ }
79
+ export function buildLandingPreference(inputs) {
80
+ const byMap = new Map();
81
+ for (const input of inputs)
82
+ byMap.set(input.match.mapName || '未知地图', [
83
+ ...(byMap.get(input.match.mapName || '未知地图') ?? []),
84
+ input,
85
+ ]);
86
+ const maps = [...byMap.entries()]
87
+ .map(([mapName, items]) => {
88
+ const withLanding = items.filter((item) => item.landings.length > 0);
89
+ const perMatch = withLanding.map((item) => {
90
+ const players = item.match.players.filter((player) => item.memberIds.has(player.accountId));
91
+ return {
92
+ cell: modalCell(item.landings),
93
+ region: item.region ?? null,
94
+ placement: placementOf(players),
95
+ earlyWipe: players.length > 0 &&
96
+ Math.max(...players.map((player) => player.survivalTime)) < LANDING_EARLY_WIPE_SECONDS,
97
+ damage: players.length
98
+ ? players.reduce((sum, player) => sum + player.damage, 0) / players.length
99
+ : 0,
100
+ };
101
+ });
102
+ const cells = new Map();
103
+ for (const row of perMatch)
104
+ if (row.cell)
105
+ cells.set(row.cell, [...(cells.get(row.cell) ?? []), row]);
106
+ const cellStats = [...cells.entries()]
107
+ .map(([cell, rows]) => {
108
+ const ranked = rows.filter((row) => row.placement !== null);
109
+ const regions = new Map();
110
+ for (const row of rows)
111
+ if (row.region)
112
+ regions.set(row.region, (regions.get(row.region) ?? 0) + 1);
113
+ return {
114
+ cell,
115
+ region: [...regions.entries()].sort((left, right) => right[1] - left[1] || left[0].localeCompare(right[0]))[0]?.[0] ?? null,
116
+ matches: rows.length,
117
+ share: rows.length / perMatch.length,
118
+ top10: wilsonInterval(ranked.filter((row) => row.placement <= 10).length, ranked.length),
119
+ medianPlacement: median(ranked.map((row) => row.placement)),
120
+ earlyWipeRate: rows.length
121
+ ? rows.filter((row) => row.earlyWipe).length / rows.length
122
+ : null,
123
+ averageDamage: rows.reduce((sum, row) => sum + row.damage, 0) / rows.length,
124
+ };
125
+ })
126
+ .sort((left, right) => right.matches - left.matches || left.cell.localeCompare(right.cell));
127
+ const spreads = withLanding.flatMap((item) => {
128
+ if (item.landings.length < 2)
129
+ return [];
130
+ const cx = item.landings.reduce((sum, landing) => sum + landing.xMeters, 0) / item.landings.length;
131
+ const cy = item.landings.reduce((sum, landing) => sum + landing.yMeters, 0) / item.landings.length;
132
+ return item.landings.map((landing) => Math.hypot(landing.xMeters - cx, landing.yMeters - cy));
133
+ });
134
+ return {
135
+ mapName,
136
+ label: mapDisplayName(mapName),
137
+ sizeKm: mapSizeKm(mapName),
138
+ matches: items.length,
139
+ matchesWithLanding: withLanding.length,
140
+ cells: cellStats,
141
+ topCellRepeatRate: cellStats.length ? cellStats[0].matches / perMatch.length : null,
142
+ medianSpreadMeters: median(spreads),
143
+ points: withLanding.flatMap((item) => item.landings.map((landing) => ({
144
+ x: Math.round(landing.xMeters),
145
+ y: Math.round(landing.yMeters),
146
+ }))),
147
+ };
148
+ })
149
+ .sort((left, right) => right.matches - left.matches || left.mapName.localeCompare(right.mapName));
150
+ const contestRows = inputs
151
+ .filter((input) => input.landings.length > 0 &&
152
+ input.contestedTeams !== null &&
153
+ input.contestedTeams !== undefined)
154
+ .map((input) => {
155
+ const players = input.match.players.filter((player) => input.memberIds.has(player.accountId));
156
+ return {
157
+ contested: input.contestedTeams,
158
+ placement: placementOf(players),
159
+ earlyWipe: players.length > 0 &&
160
+ Math.max(...players.map((player) => player.survivalTime)) < LANDING_EARLY_WIPE_SECONDS,
161
+ };
162
+ });
163
+ const bucketOf = (key, label, rows) => {
164
+ const ranked = rows.filter((row) => row.placement !== null);
165
+ return {
166
+ key,
167
+ label,
168
+ matches: rows.length,
169
+ top10: wilsonInterval(ranked.filter((row) => row.placement <= 10).length, ranked.length),
170
+ earlyWipe: wilsonInterval(rows.filter((row) => row.earlyWipe).length, rows.length),
171
+ medianPlacement: median(ranked.map((row) => row.placement)),
172
+ };
173
+ };
174
+ const calm = bucketOf('calm', '无队伍争抢', contestRows.filter((row) => row.contested === 0));
175
+ const crowded = bucketOf('crowded', '有队伍争抢', contestRows.filter((row) => row.contested >= 1));
176
+ const enough = calm.matches >= CONTEST_MIN_MATCHES && crowded.matches >= CONTEST_MIN_MATCHES;
177
+ const none = newcombeDifference(wilsonInterval(0, 0), wilsonInterval(0, 0));
178
+ const contest = {
179
+ matches: contestRows.length,
180
+ buckets: [
181
+ calm,
182
+ bucketOf('one', '1 队争抢', contestRows.filter((row) => row.contested === 1)),
183
+ bucketOf('many', '2 队及以上争抢', contestRows.filter((row) => row.contested >= 2)),
184
+ ],
185
+ earlyWipeEdge: enough ? newcombeDifference(crowded.earlyWipe, calm.earlyWipe) : none,
186
+ top10Edge: enough ? newcombeDifference(crowded.top10, calm.top10) : none,
187
+ };
188
+ const notes = [];
189
+ const missing = inputs.length - inputs.filter((item) => item.landings.length > 0).length;
190
+ if (missing > 0)
191
+ notes.push(`${missing} 场没有可读的落地事件,未计入落点统计。`);
192
+ notes.push(`落点按 ${LANDING_CELL_KM}km 方格归类;早崩 = 全队最长生存时间不足 ${LANDING_EARLY_WIPE_SECONDS / 60} 分钟。落点与名次只是相关性,不说明因果。`);
193
+ return { schemaVersion: LANDING_SCHEMA_VERSION, maps, contest, notes };
194
+ }
195
+ //# sourceMappingURL=landing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"landing.js","sourceRoot":"","sources":["../../src/landing.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,48 @@
1
+ export declare const LOBBY_SCHEMA_VERSION: 1;
2
+ /** Players who left the ground for less than this are dropped from the lobby so early drops do not skew percentiles. */
3
+ export declare const LOBBY_MIN_ALIVE_MINUTES = 2;
4
+ export declare const LOBBY_MIN_MATCHES = 20;
5
+ export declare const LOBBY_EVEN_BAND = 0.03;
6
+ export interface LobbyMatchStats {
7
+ aliveMinutes: number;
8
+ damagePerMinute: number;
9
+ knocksPerMinute: number;
10
+ /** Position of the player's rate among every eligible player in the match, 0 (lowest) to 1 (highest). */
11
+ damagePercentile: number | null;
12
+ knockPercentile: number | null;
13
+ lobbySize: number;
14
+ }
15
+ /**
16
+ * Rebuilds the whole lobby from one raw payload (damage dealt to other teams, knocks and time on the ground for every
17
+ * player) and places each tracked member among them on damage and knocks per minute alive.
18
+ */
19
+ export declare function analyzeLobby(payload: unknown, trackedIds: ReadonlySet<string>): Record<string, LobbyMatchStats>;
20
+ export interface LobbyInterval {
21
+ mean: number | null;
22
+ low: number | null;
23
+ high: number | null;
24
+ }
25
+ export type LobbyTier = 'above' | 'below' | 'even' | 'unknown';
26
+ export interface LobbyEntry {
27
+ accountId: string;
28
+ name: string;
29
+ matches: number;
30
+ rank: number;
31
+ damage: LobbyInterval;
32
+ knocks: LobbyInterval;
33
+ /** Mean of the two percentiles per match. */
34
+ composite: LobbyInterval;
35
+ /** Against the lobby median (0.5): above or below only when the interval clears it by a margin. */
36
+ tier: LobbyTier;
37
+ medianDamagePerMinute: number | null;
38
+ }
39
+ export interface LobbyCombat {
40
+ schemaVersion: typeof LOBBY_SCHEMA_VERSION;
41
+ matches: number;
42
+ entries: LobbyEntry[];
43
+ /** Mean lobby size of the analysed matches. */
44
+ lobbySize: number | null;
45
+ verdict: string;
46
+ notes: string[];
47
+ }
48
+ export declare function summarizeLobby(items: ReadonlyArray<Record<string, LobbyMatchStats>>, names: ReadonlyMap<string, string>): LobbyCombat;
@@ -0,0 +1,209 @@
1
+ import { parseTelemetryPayload } from '@rine-kit/pubg-domain';
2
+ import { EXPLOSIVE_DAMAGE, mulberry32, percentile, STYLE_BOOTSTRAP_RESAMPLES, STYLE_BOOTSTRAP_SEED, } from './style.js';
3
+ export const LOBBY_SCHEMA_VERSION = 1;
4
+ /** Players who left the ground for less than this are dropped from the lobby so early drops do not skew percentiles. */
5
+ export const LOBBY_MIN_ALIVE_MINUTES = 2;
6
+ export const LOBBY_MIN_MATCHES = 20;
7
+ export const LOBBY_EVEN_BAND = 0.03;
8
+ function obj(value) {
9
+ return value && typeof value === 'object' && !Array.isArray(value)
10
+ ? value
11
+ : {};
12
+ }
13
+ function num(value) {
14
+ return typeof value === 'number' && Number.isFinite(value) ? value : null;
15
+ }
16
+ function midrankPercentile(values, value) {
17
+ const less = values.filter((other) => other < value).length;
18
+ const equal = values.filter((other) => other === value).length;
19
+ return (less + 0.5 * equal) / values.length;
20
+ }
21
+ /**
22
+ * Rebuilds the whole lobby from one raw payload (damage dealt to other teams, knocks and time on the ground for every
23
+ * player) and places each tracked member among them on damage and knocks per minute alive.
24
+ */
25
+ export function analyzeLobby(payload, trackedIds) {
26
+ const players = new Map();
27
+ for (const raw of parseTelemetryPayload(payload)) {
28
+ const event = obj(raw);
29
+ const type = event._T;
30
+ const time = typeof event._D === 'string' ? Date.parse(event._D) : NaN;
31
+ if (!Number.isFinite(time))
32
+ continue;
33
+ const phase = num(obj(event.common).isGame);
34
+ if (type === 'LogParachuteLanding') {
35
+ const character = obj(event.character);
36
+ const id = typeof character.accountId === 'string' ? character.accountId : null;
37
+ if (id && !players.has(id))
38
+ players.set(id, {
39
+ team: String(character.teamId ?? ''),
40
+ user: character.type === 'user',
41
+ landed: time,
42
+ seen: time,
43
+ end: null,
44
+ damage: 0,
45
+ knocks: 0,
46
+ });
47
+ }
48
+ else if (type === 'LogPlayerPosition') {
49
+ const id = typeof obj(event.character).accountId === 'string'
50
+ ? obj(event.character).accountId
51
+ : null;
52
+ const entry = id ? players.get(id) : undefined;
53
+ if (entry && time > entry.seen)
54
+ entry.seen = time;
55
+ }
56
+ else if (type === 'LogPlayerTakeDamage') {
57
+ const category = typeof event.damageTypeCategory === 'string' ? event.damageTypeCategory : '';
58
+ if (phase === null ||
59
+ phase < 1 ||
60
+ (category !== 'Damage_Gun' && !EXPLOSIVE_DAMAGE.has(category)))
61
+ continue;
62
+ const damage = num(event.damage);
63
+ const attacker = obj(event.attacker);
64
+ const victim = obj(event.victim);
65
+ const id = typeof attacker.accountId === 'string' ? attacker.accountId : null;
66
+ const entry = id ? players.get(id) : undefined;
67
+ if (entry &&
68
+ damage !== null &&
69
+ damage > 0 &&
70
+ String(attacker.teamId ?? '') !== String(victim.teamId ?? ''))
71
+ entry.damage += damage;
72
+ }
73
+ else if (type === 'LogPlayerMakeGroggy') {
74
+ const attacker = obj(event.attacker);
75
+ const victim = obj(event.victim);
76
+ const id = typeof attacker.accountId === 'string' ? attacker.accountId : null;
77
+ const entry = id ? players.get(id) : undefined;
78
+ if (entry && String(attacker.teamId ?? '') !== String(victim.teamId ?? ''))
79
+ entry.knocks += 1;
80
+ }
81
+ else if (type === 'LogPlayerKill' || type === 'LogPlayerKillV2') {
82
+ const id = typeof obj(event.victim).accountId === 'string'
83
+ ? obj(event.victim).accountId
84
+ : null;
85
+ const entry = id ? players.get(id) : undefined;
86
+ if (entry && entry.end === null)
87
+ entry.end = time;
88
+ }
89
+ }
90
+ const rows = [...players.entries()].map(([id, entry]) => {
91
+ const endAt = entry.end ?? entry.seen;
92
+ const aliveMinutes = Math.max(0, (endAt - entry.landed) / 60000);
93
+ return {
94
+ id,
95
+ aliveMinutes,
96
+ dpm: aliveMinutes > 0 ? entry.damage / aliveMinutes : 0,
97
+ kpm: aliveMinutes > 0 ? entry.knocks / aliveMinutes : 0,
98
+ eligible: entry.user && aliveMinutes >= LOBBY_MIN_ALIVE_MINUTES,
99
+ };
100
+ });
101
+ const lobby = rows.filter((row) => row.eligible);
102
+ const result = {};
103
+ for (const row of rows) {
104
+ if (!trackedIds.has(row.id))
105
+ continue;
106
+ result[row.id] = {
107
+ aliveMinutes: row.aliveMinutes,
108
+ damagePerMinute: row.dpm,
109
+ knocksPerMinute: row.kpm,
110
+ damagePercentile: row.eligible && lobby.length >= 10
111
+ ? midrankPercentile(lobby.map((other) => other.dpm), row.dpm)
112
+ : null,
113
+ knockPercentile: row.eligible && lobby.length >= 10
114
+ ? midrankPercentile(lobby.map((other) => other.kpm), row.kpm)
115
+ : null,
116
+ lobbySize: lobby.length,
117
+ };
118
+ }
119
+ return result;
120
+ }
121
+ function bootstrapMean(values, seed) {
122
+ if (values.length === 0)
123
+ return { mean: null, low: null, high: null };
124
+ const mean = values.reduce((sum, value) => sum + value, 0) / values.length;
125
+ if (values.length < LOBBY_MIN_MATCHES)
126
+ return { mean, low: null, high: null };
127
+ const rng = mulberry32(seed);
128
+ const means = [];
129
+ for (let round = 0; round < STYLE_BOOTSTRAP_RESAMPLES; round += 1) {
130
+ let total = 0;
131
+ for (let index = 0; index < values.length; index += 1)
132
+ total += values[Math.floor(rng() * values.length)];
133
+ means.push(total / values.length);
134
+ }
135
+ means.sort((left, right) => left - right);
136
+ return { mean, low: percentile(means, 0.025), high: percentile(means, 0.975) };
137
+ }
138
+ function medianOf(values) {
139
+ if (!values.length)
140
+ return null;
141
+ const sorted = [...values].sort((left, right) => left - right);
142
+ const mid = Math.floor(sorted.length / 2);
143
+ return sorted.length % 2 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2;
144
+ }
145
+ export function summarizeLobby(items, names) {
146
+ const perPlayer = new Map();
147
+ const sizes = [];
148
+ for (const item of items) {
149
+ for (const [id, stats] of Object.entries(item)) {
150
+ if (stats.damagePercentile === null || stats.knockPercentile === null)
151
+ continue;
152
+ perPlayer.set(id, [...(perPlayer.get(id) ?? []), stats]);
153
+ sizes.push(stats.lobbySize);
154
+ }
155
+ }
156
+ const entries = [...perPlayer.entries()]
157
+ .map(([accountId, rows], index) => {
158
+ const damage = bootstrapMean(rows.map((row) => row.damagePercentile), STYLE_BOOTSTRAP_SEED + 11 + index);
159
+ const knocks = bootstrapMean(rows.map((row) => row.knockPercentile), STYLE_BOOTSTRAP_SEED + 31 + index);
160
+ const composite = bootstrapMean(rows.map((row) => (row.damagePercentile + row.knockPercentile) / 2), STYLE_BOOTSTRAP_SEED + 53 + index);
161
+ const tier = composite.mean === null || composite.low === null || composite.high === null
162
+ ? 'unknown'
163
+ : composite.low > 0.5 + LOBBY_EVEN_BAND
164
+ ? 'above'
165
+ : composite.high < 0.5 - LOBBY_EVEN_BAND
166
+ ? 'below'
167
+ : 'even';
168
+ return {
169
+ accountId,
170
+ name: names.get(accountId) ?? '未知',
171
+ matches: rows.length,
172
+ rank: 0,
173
+ damage,
174
+ knocks,
175
+ composite,
176
+ tier,
177
+ medianDamagePerMinute: medianOf(rows.map((row) => row.damagePerMinute)),
178
+ };
179
+ })
180
+ .sort((left, right) => (right.composite.mean ?? -1) - (left.composite.mean ?? -1) ||
181
+ left.name.localeCompare(right.name));
182
+ entries.forEach((entry, index) => {
183
+ entry.rank = index + 1;
184
+ });
185
+ const leader = entries[0];
186
+ const second = entries[1];
187
+ const separated = leader &&
188
+ second &&
189
+ leader.composite.low !== null &&
190
+ second.composite.high !== null &&
191
+ leader.composite.low > second.composite.high;
192
+ const verdict = !leader || leader.composite.mean === null || items.length < LOBBY_MIN_MATCHES
193
+ ? `有可用全场数据的比赛不足 ${LOBBY_MIN_MATCHES} 场,暂不比较。`
194
+ : separated
195
+ ? `在全场约 ${Math.round(sizes.reduce((sum, value) => sum + value, 0) / Math.max(1, sizes.length))} 名玩家里,${leader.name} 的输出分位明显高于其他队友(${(leader.composite.mean * 100).toFixed(0)} 对 ${(second.composite.mean * 100).toFixed(0)},区间不重叠)。`
196
+ : `在全场玩家里,四人的输出分位差距在抽样误差内(最高 ${leader.name} ${(leader.composite.mean * 100).toFixed(0)},最低 ${entries.at(-1).name} ${((entries.at(-1).composite.mean ?? 0) * 100).toFixed(0)}),不能断定谁更强。`;
197
+ return {
198
+ schemaVersion: LOBBY_SCHEMA_VERSION,
199
+ matches: items.length,
200
+ entries,
201
+ lobbySize: sizes.length ? sizes.reduce((sum, value) => sum + value, 0) / sizes.length : null,
202
+ verdict,
203
+ notes: [
204
+ `分位 = 该队员每存活分钟的枪械伤害(或击倒)在本场所有真人玩家里的位置,0.50 即全场中位数;只统计落地后至少 ${LOBBY_MIN_ALIVE_MINUTES} 分钟的玩家,避免开局就被淘汰的人把分布拉偏。`,
205
+ '综合 = 伤害分位与击倒分位的平均;区间由按场次重抽样得到。分位以每分钟速率计算,对存活时间长短的偏差已做归一,但同一场里活得更久的人面对的对手更强,所以不要把它当作绝对水平。',
206
+ ],
207
+ };
208
+ }
209
+ //# sourceMappingURL=lobby.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lobby.js","sourceRoot":"","sources":["../../src/lobby.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,165 @@
1
+ import { type RateEstimate } from './analytics.js';
2
+ import type { MatchStory, StoryEvent, StoryFight } from './match-story.js';
3
+ export declare const MATCH_INSIGHT_SCHEMA_VERSION: 1;
4
+ /** How far back a down looks for the fire that caused it. */
5
+ export declare const DOWN_LOOKBACK_SECONDS = 20;
6
+ /** A shot taken from at least this far is a long-range pick. */
7
+ export declare const DOWN_FAR_METERS = 150;
8
+ /** A member this far from every teammate is on their own. */
9
+ export declare const DOWN_ALONE_METERS = 150;
10
+ /** Opposing players within this distance count as nearby. */
11
+ export declare const DOWN_NEARBY_METERS = 300;
12
+ /** One contact between the squad and an opposing team, as collected while reading the telemetry. */
13
+ export interface StoryContact {
14
+ t: number;
15
+ kind: 'dealt' | 'taken' | 'knock' | 'downed' | 'kill' | 'revive';
16
+ member: string;
17
+ enemyTeam: string | null;
18
+ amount: number;
19
+ victim: string | null;
20
+ weapon?: string | null;
21
+ }
22
+ export interface BlueDamage {
23
+ t: number;
24
+ member: string;
25
+ amount: number;
26
+ }
27
+ export type DownCause = 'ambush_far' | 'ambush_close' | 'crossfire' | 'isolated' | 'lost_duel' | 'bluezone' | 'environment';
28
+ export declare const DOWN_CAUSES: ReadonlyArray<{
29
+ key: DownCause;
30
+ label: string;
31
+ advice: string;
32
+ }>;
33
+ export interface StoryDown {
34
+ t: number;
35
+ member: string;
36
+ cause: DownCause;
37
+ attackerTeam: string | null;
38
+ weapon: string | null;
39
+ meters: number | null;
40
+ /** Damage the squad dealt to the attacking team in the lookback window (0 = no return fire). */
41
+ returnDamage: number;
42
+ /** Damage the squad took from opposing teams in the lookback window. */
43
+ takenDamage: number;
44
+ /** Distinct opposing teams that damaged the squad in the lookback window. */
45
+ teamsHitting: number;
46
+ nearestTeammate: number | null;
47
+ alone: boolean | null;
48
+ /** Opposing teams with a player within DOWN_NEARBY_METERS when the down happened. */
49
+ teamsNearby: number | null;
50
+ phase: number | null;
51
+ outcome: 'revived' | 'died' | 'unknown';
52
+ /** Seconds from the down to the revive or the death. */
53
+ afterSeconds: number | null;
54
+ }
55
+ export declare function buildDowns(story: MatchStory, contacts: readonly StoryContact[], blueDamage: readonly BlueDamage[]): StoryDown[];
56
+ export interface TurningPoint {
57
+ kind: 'first_loss' | 'collapse' | 'costly' | 'decisive' | 'final_fight';
58
+ t: number;
59
+ fight: number | null;
60
+ member: string | null;
61
+ /** Consecutive fights in a collapse, or the down/takedown balance of a single fight. */
62
+ count: number;
63
+ }
64
+ export declare function buildTurningPoints(story: MatchStory): TurningPoint[];
65
+ /**
66
+ * In the telemetry `poisonGasWarning*` is the NEXT safe circle (the white circle, fixed for a whole phase) and
67
+ * `safetyZone*` is the gas wall closing in on it (the blue circle, shrinking once the phase starts to close).
68
+ * A squad is late into a circle when it is not inside the white circle by the time the blue wall starts to move.
69
+ */
70
+ export interface RotationEpoch {
71
+ phase: number | null;
72
+ start: number;
73
+ end: number;
74
+ radius: number;
75
+ /** How far outside the new safe circle the squad stood when it appeared (0 = already inside). */
76
+ outsideAtStart: number;
77
+ /** Seconds from the circle appearing to the squad being inside it; null when it never got in. */
78
+ enteredAfter: number | null;
79
+ /** Seconds from the circle appearing to the gas wall starting to close; null in the final phases that never close. */
80
+ shrinkAfter: number | null;
81
+ /** Seconds the squad was inside before the wall moved (negative = the wall caught it); null when either is unknown. */
82
+ margin: number | null;
83
+ blueSeconds: number;
84
+ blueDamage: number;
85
+ distance: number;
86
+ vehicleShare: number | null;
87
+ }
88
+ export interface MatchRotation {
89
+ epochs: RotationEpoch[];
90
+ blueSeconds: number;
91
+ blueDamage: number;
92
+ distance: number;
93
+ vehicleShare: number | null;
94
+ /** Seconds from the first landing to the first fight. */
95
+ landingToFight: number | null;
96
+ /** Circles from phase 3 on that the squad had not reached when the wall started to close (or never reached). */
97
+ lateEntries: number;
98
+ circlesChecked: number;
99
+ }
100
+ export declare function buildRotation(story: MatchStory, blueDamage: readonly BlueDamage[]): MatchRotation;
101
+ export interface DownSummary {
102
+ matches: number;
103
+ downs: number;
104
+ byCause: Array<{
105
+ cause: DownCause;
106
+ label: string;
107
+ advice: string;
108
+ count: number;
109
+ share: number;
110
+ died: number;
111
+ }>;
112
+ alone: RateEstimate;
113
+ /** Downs from which the member was brought back. */
114
+ revived: RateEstimate;
115
+ noReturnFire: RateEstimate;
116
+ byMember: Array<{
117
+ member: string;
118
+ downs: number;
119
+ causes: Array<{
120
+ cause: DownCause;
121
+ count: number;
122
+ }>;
123
+ }>;
124
+ byStage: Array<{
125
+ stage: 'early' | 'mid' | 'late';
126
+ downs: number;
127
+ noReturnFire: number;
128
+ }>;
129
+ insights: string[];
130
+ }
131
+ export declare function summarizeDowns(items: ReadonlyArray<{
132
+ downs: StoryDown[];
133
+ }>, names: ReadonlyMap<string, string>): DownSummary;
134
+ export interface RotationSummary {
135
+ matches: number;
136
+ /** Matches in which the squad took any damage from the circle. */
137
+ bluePenaltyRate: RateEstimate;
138
+ averageBlueDamage: number | null;
139
+ averageBlueSeconds: number | null;
140
+ averageDistanceKm: number | null;
141
+ vehicleShare: number | null;
142
+ averageLandingToFight: number | null;
143
+ lateEntry: RateEstimate;
144
+ /** Average seconds the squad was inside before the wall moved (negative = caught by it). */
145
+ averageMargin: number | null;
146
+ byPhase: Array<{
147
+ phase: number;
148
+ circles: number;
149
+ lateRate: number | null;
150
+ medianEnter: number | null;
151
+ averageMargin: number | null;
152
+ averageOutside: number | null;
153
+ averageDistance: number | null;
154
+ }>;
155
+ insights: string[];
156
+ }
157
+ /**
158
+ * Descriptive only. Comparing placement between matches with much and little gas damage would be misleading: a squad
159
+ * that survives longer goes through more circles and so takes more gas, which makes the gas look good for placement.
160
+ */
161
+ export declare function summarizeRotation(items: ReadonlyArray<{
162
+ rotation: MatchRotation;
163
+ }>): RotationSummary;
164
+ export declare function downCauseLabel(cause: DownCause): string;
165
+ export type { StoryEvent, StoryFight };