@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,298 @@
1
+ import { parseTelemetryPayload } from '@rine-kit/pubg-domain';
2
+ import { normalizeWeaponName, wilsonInterval } from './analytics.js';
3
+ export const FIELD_SCHEMA_VERSION = 1;
4
+ /** Engagement distance bands in metres; the last band is open ended. */
5
+ export const DISTANCE_BANDS = [
6
+ { id: 'close', label: '近距 <25m', maxMeters: 25 },
7
+ { id: 'mid', label: '中近 25–75m', maxMeters: 75 },
8
+ { id: 'far', label: '中远 75–150m', maxMeters: 150 },
9
+ { id: 'long', label: '远距 ≥150m', maxMeters: Infinity },
10
+ ];
11
+ export const DISTANCE_MIN_DAMAGE = 600;
12
+ export const DISTANCE_GAP_POINTS = 0.1;
13
+ export const ZONE_MAX_PHASE = 9;
14
+ export const ZONE_MIN_SAMPLES = 150;
15
+ export const ZONE_BIN_EDGES = [0.5, 0.85, 1];
16
+ export const CONTEST_RADIUS_METERS = 500;
17
+ export const CONTEST_MIN_MATCHES = 8;
18
+ const BAND_COUNT = DISTANCE_BANDS.length;
19
+ export function distanceBandOf(meters) {
20
+ const index = DISTANCE_BANDS.findIndex((band) => meters < band.maxMeters);
21
+ return index < 0 ? BAND_COUNT - 1 : index;
22
+ }
23
+ function obj(value) {
24
+ return value && typeof value === 'object' && !Array.isArray(value)
25
+ ? value
26
+ : {};
27
+ }
28
+ function num(value) {
29
+ return typeof value === 'number' && Number.isFinite(value) ? value : null;
30
+ }
31
+ function point(value) {
32
+ const location = obj(value);
33
+ const x = num(location.x);
34
+ const y = num(location.y);
35
+ if (x === null || y === null)
36
+ return null;
37
+ return { x, y, z: num(location.z) ?? 0 };
38
+ }
39
+ function inMatchPhase(event) {
40
+ const value = num(obj(event.common).isGame);
41
+ return value !== null && value >= 1 ? value : null;
42
+ }
43
+ function emptyPhase() {
44
+ return { samples: 0, blueSamples: 0, bins: [0, 0, 0, 0], blueDamage: 0 };
45
+ }
46
+ /**
47
+ * One pass over a raw telemetry payload for the tracked squad: damage by engagement distance, time spent
48
+ * outside the safe zone, blue-zone damage and how crowded the landing spot was.
49
+ */
50
+ export function analyzeFieldTelemetry(payload, trackedIds) {
51
+ const stats = {
52
+ distance: {
53
+ dealt: new Array(BAND_COUNT).fill(0),
54
+ dealtHead: new Array(BAND_COUNT).fill(0),
55
+ taken: new Array(BAND_COUNT).fill(0),
56
+ takenHead: new Array(BAND_COUNT).fill(0),
57
+ weapons: {},
58
+ },
59
+ zone: { phases: {}, blueDamage: 0, damageTaken: 0 },
60
+ landing: { contestedTeams: null, nearestEnemyMeters: null, region: null },
61
+ };
62
+ const phase = (value) => {
63
+ const key = Math.min(ZONE_MAX_PHASE, Math.max(1, Math.floor(value)));
64
+ return (stats.zone.phases[key] ??= emptyPhase());
65
+ };
66
+ let safety = null;
67
+ const landings = [];
68
+ const landedAt = new Map();
69
+ const regions = new Map();
70
+ const regionSeen = new Set();
71
+ for (const raw of parseTelemetryPayload(payload)) {
72
+ const event = obj(raw);
73
+ const type = event._T;
74
+ const time = typeof event._D === 'string' ? Date.parse(event._D) : NaN;
75
+ if (type === 'LogGameStatePeriodic') {
76
+ if (inMatchPhase(event) === null)
77
+ continue;
78
+ const state = obj(event.gameState);
79
+ const center = point(state.safetyZonePosition);
80
+ const radius = num(state.safetyZoneRadius);
81
+ if (center && radius !== null && radius > 0)
82
+ safety = { center, radius };
83
+ continue;
84
+ }
85
+ if (type === 'LogParachuteLanding') {
86
+ const character = obj(event.character);
87
+ const accountId = typeof character.accountId === 'string' ? character.accountId : null;
88
+ const location = point(character.location);
89
+ if (!accountId || !location)
90
+ continue;
91
+ landings.push({
92
+ accountId,
93
+ teamId: String(character.teamId ?? ''),
94
+ x: location.x / 100,
95
+ y: location.y / 100,
96
+ time,
97
+ });
98
+ if (trackedIds.has(accountId))
99
+ landedAt.set(accountId, time);
100
+ continue;
101
+ }
102
+ if (type === 'LogPlayerPosition') {
103
+ const character = obj(event.character);
104
+ const accountId = typeof character.accountId === 'string' ? character.accountId : null;
105
+ if (!accountId || !trackedIds.has(accountId))
106
+ continue;
107
+ const landed = landedAt.get(accountId);
108
+ if (landed !== undefined && !regionSeen.has(accountId) && time >= landed) {
109
+ const zones = Array.isArray(character.zone) ? character.zone : [];
110
+ const name = typeof zones[0] === 'string' ? zones[0] : null;
111
+ if (name) {
112
+ regionSeen.add(accountId);
113
+ regions.set(name, (regions.get(name) ?? 0) + 1);
114
+ }
115
+ }
116
+ const phaseValue = inMatchPhase(event);
117
+ const location = point(character.location);
118
+ if (phaseValue === null || !location)
119
+ continue;
120
+ const entry = phase(phaseValue);
121
+ entry.samples += 1;
122
+ if (character.isInBlueZone === true)
123
+ entry.blueSamples += 1;
124
+ if (safety) {
125
+ const ratio = Math.hypot(location.x - safety.center.x, location.y - safety.center.y) / safety.radius;
126
+ const bin = ratio < ZONE_BIN_EDGES[0]
127
+ ? 0
128
+ : ratio < ZONE_BIN_EDGES[1]
129
+ ? 1
130
+ : ratio <= ZONE_BIN_EDGES[2]
131
+ ? 2
132
+ : 3;
133
+ entry.bins[bin] += 1;
134
+ }
135
+ continue;
136
+ }
137
+ if (type === 'LogPlayerTakeDamage') {
138
+ const phaseValue = inMatchPhase(event);
139
+ if (phaseValue === null)
140
+ continue;
141
+ const damage = num(event.damage);
142
+ if (damage === null || damage <= 0)
143
+ continue;
144
+ const attacker = obj(event.attacker);
145
+ const victim = obj(event.victim);
146
+ const victimId = typeof victim.accountId === 'string' ? victim.accountId : null;
147
+ const attackerId = typeof attacker.accountId === 'string' ? attacker.accountId : null;
148
+ const victimTracked = victimId !== null && trackedIds.has(victimId);
149
+ const attackerTracked = attackerId !== null && trackedIds.has(attackerId);
150
+ const category = typeof event.damageTypeCategory === 'string' ? event.damageTypeCategory : '';
151
+ if (victimTracked) {
152
+ stats.zone.damageTaken += damage;
153
+ if (category === 'Damage_BlueZone' || category === 'Damage_BlueZoneGrenade') {
154
+ stats.zone.blueDamage += damage;
155
+ phase(phaseValue).blueDamage += damage;
156
+ }
157
+ }
158
+ if (category !== 'Damage_Gun' || victimTracked === attackerTracked)
159
+ continue;
160
+ const from = point(attacker.location);
161
+ const to = point(victim.location);
162
+ if (!from || !to)
163
+ continue;
164
+ if (String(attacker.teamId ?? '') === String(victim.teamId ?? ''))
165
+ continue;
166
+ const meters = Math.hypot(from.x - to.x, from.y - to.y, from.z - to.z) / 100;
167
+ const band = distanceBandOf(meters);
168
+ const head = event.damageReason === 'HeadShot';
169
+ if (attackerTracked) {
170
+ stats.distance.dealt[band] += damage;
171
+ if (head)
172
+ stats.distance.dealtHead[band] += damage;
173
+ const weapon = typeof event.damageCauserName === 'string'
174
+ ? normalizeWeaponName(event.damageCauserName)
175
+ : null;
176
+ if (weapon)
177
+ (stats.distance.weapons[weapon] ??= new Array(BAND_COUNT).fill(0))[band] +=
178
+ damage;
179
+ }
180
+ else {
181
+ stats.distance.taken[band] += damage;
182
+ if (head)
183
+ stats.distance.takenHead[band] += damage;
184
+ }
185
+ }
186
+ }
187
+ const mine = landings.filter((landing) => trackedIds.has(landing.accountId));
188
+ if (mine.length) {
189
+ const cx = mine.reduce((sum, landing) => sum + landing.x, 0) / mine.length;
190
+ const cy = mine.reduce((sum, landing) => sum + landing.y, 0) / mine.length;
191
+ const ourTeams = new Set(mine.map((landing) => landing.teamId));
192
+ const nearestByTeam = new Map();
193
+ for (const landing of landings) {
194
+ if (ourTeams.has(landing.teamId) || trackedIds.has(landing.accountId))
195
+ continue;
196
+ const distance = Math.hypot(landing.x - cx, landing.y - cy);
197
+ nearestByTeam.set(landing.teamId, Math.min(nearestByTeam.get(landing.teamId) ?? Infinity, distance));
198
+ }
199
+ const nearest = [...nearestByTeam.values()];
200
+ stats.landing.contestedTeams = nearest.filter((distance) => distance <= CONTEST_RADIUS_METERS).length;
201
+ stats.landing.nearestEnemyMeters = nearest.length ? Math.min(...nearest) : null;
202
+ stats.landing.region =
203
+ [...regions.entries()].sort((left, right) => right[1] - left[1] || left[0].localeCompare(right[0]))[0]?.[0] ?? null;
204
+ }
205
+ return stats;
206
+ }
207
+ export function summarizeDistance(items) {
208
+ const sumBand = (pick) => Array.from({ length: BAND_COUNT }, (_, band) => items.reduce((total, item) => total + (pick(item)[band] ?? 0), 0));
209
+ const dealt = sumBand((item) => item.distance.dealt);
210
+ const taken = sumBand((item) => item.distance.taken);
211
+ const head = sumBand((item) => item.distance.dealtHead);
212
+ const totalDealt = dealt.reduce((sum, value) => sum + value, 0);
213
+ const totalTaken = taken.reduce((sum, value) => sum + value, 0);
214
+ const weapons = new Map();
215
+ for (const item of items) {
216
+ for (const [weapon, bands] of Object.entries(item.distance.weapons)) {
217
+ const current = weapons.get(weapon) ?? new Array(BAND_COUNT).fill(0);
218
+ bands.forEach((value, index) => {
219
+ current[index] += value;
220
+ });
221
+ weapons.set(weapon, current);
222
+ }
223
+ }
224
+ const longShare = (values, total) => total > 0 ? (values[2] + values[3]) / total : null;
225
+ const dealtLong = longShare(dealt, totalDealt);
226
+ const takenLong = longShare(taken, totalTaken);
227
+ return {
228
+ matches: items.length,
229
+ totalDealt,
230
+ totalTaken,
231
+ bands: DISTANCE_BANDS.map((band, index) => ({
232
+ id: band.id,
233
+ label: band.label,
234
+ dealt: dealt[index],
235
+ taken: taken[index],
236
+ dealtShare: totalDealt > 0 ? dealt[index] / totalDealt : null,
237
+ takenShare: totalTaken > 0 ? taken[index] / totalTaken : null,
238
+ headshotShare: dealt[index] > 0 ? head[index] / dealt[index] : null,
239
+ })),
240
+ weapons: [...weapons.entries()]
241
+ .map(([weapon, bands]) => {
242
+ const total = bands.reduce((sum, value) => sum + value, 0);
243
+ const main = bands.indexOf(Math.max(...bands));
244
+ return {
245
+ weapon,
246
+ damage: total,
247
+ shares: bands.map((value) => (total > 0 ? value / total : 0)),
248
+ mainBand: DISTANCE_BANDS[main].label,
249
+ };
250
+ })
251
+ .filter((entry) => entry.damage >= 200)
252
+ .sort((left, right) => right.damage - left.damage || left.weapon.localeCompare(right.weapon))
253
+ .slice(0, 8),
254
+ longRangeGap: dealtLong !== null &&
255
+ takenLong !== null &&
256
+ totalDealt >= DISTANCE_MIN_DAMAGE &&
257
+ totalTaken >= DISTANCE_MIN_DAMAGE
258
+ ? takenLong - dealtLong
259
+ : null,
260
+ };
261
+ }
262
+ export function summarizeZone(items) {
263
+ const phases = new Map();
264
+ for (const item of items) {
265
+ for (const [key, value] of Object.entries(item.zone.phases)) {
266
+ const current = phases.get(Number(key)) ?? emptyPhase();
267
+ current.samples += value.samples;
268
+ current.blueSamples += value.blueSamples;
269
+ value.bins.forEach((count, index) => {
270
+ current.bins[index] += count;
271
+ });
272
+ current.blueDamage += value.blueDamage;
273
+ phases.set(Number(key), current);
274
+ }
275
+ }
276
+ const blue = items.reduce((sum, item) => sum + item.zone.blueDamage, 0);
277
+ const taken = items.reduce((sum, item) => sum + item.zone.damageTaken, 0);
278
+ return {
279
+ matches: items.length,
280
+ blueDamagePerMatch: items.length ? blue / items.length : null,
281
+ blueDamageShare: taken > 0 ? blue / taken : null,
282
+ phases: [...phases.entries()]
283
+ .sort(([left], [right]) => left - right)
284
+ .map(([phaseNumber, value]) => {
285
+ const binTotal = value.bins.reduce((sum, count) => sum + count, 0);
286
+ return {
287
+ phase: phaseNumber,
288
+ samples: value.samples,
289
+ outsideRate: wilsonInterval(value.blueSamples, value.samples),
290
+ binShares: binTotal > 0
291
+ ? value.bins.map((count) => count / binTotal)
292
+ : null,
293
+ blueDamage: value.blueDamage,
294
+ };
295
+ }),
296
+ };
297
+ }
298
+ //# sourceMappingURL=field.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"field.js","sourceRoot":"","sources":["../../src/field.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,89 @@
1
+ import { type DifferenceEstimate, type RateEstimate } from './analytics.js';
2
+ import { type BattleReconstructionResult } from './battle.js';
3
+ import { type DeathStage } from './combat-quality.js';
4
+ export declare const FIGHTS_SCHEMA_VERSION: 1;
5
+ export declare const FIGHT_MIN_GROUP = 12;
6
+ export type FightOutcome = 'win' | 'lose' | 'draw';
7
+ export type FightInitiator = 'us' | 'them' | 'unknown';
8
+ export interface FightRecord {
9
+ outcome: FightOutcome;
10
+ initiator: FightInitiator;
11
+ stage: DeathStage;
12
+ thirdParty: boolean;
13
+ /** Teams observed in the battle group, ours included. */
14
+ teams: number;
15
+ durationSeconds: number;
16
+ }
17
+ export interface RoleCounts {
18
+ fights: number;
19
+ /** Fights where this member dealt the first effective damage to an opponent. */
20
+ initiations: number;
21
+ firstKnocks: number;
22
+ firstDowned: number;
23
+ knocksDealt: number;
24
+ timesDowned: number;
25
+ kills: number;
26
+ revives: number;
27
+ /** Fights in which this member knocked at least one opponent. */
28
+ fightsWithKnock: number;
29
+ /** Knocks dealt by this member, and how many of those victims were finished off by the squad. */
30
+ chainsDealt: number;
31
+ chainsFinished: number;
32
+ }
33
+ export interface FightMatchStats {
34
+ fights: FightRecord[];
35
+ roles: Record<string, RoleCounts>;
36
+ }
37
+ export declare function emptyRoleCounts(): RoleCounts;
38
+ /**
39
+ * Win/lose is decided by unique victims on the observed timeline: a fight is won when more opponents than
40
+ * squad members were knocked or killed. Fights with no knock at all are not scored.
41
+ */
42
+ export declare function analyzeFights(result: BattleReconstructionResult, matchId: string, trackedAccountIds: readonly string[]): FightMatchStats;
43
+ export interface FightBucket {
44
+ key: string;
45
+ label: string;
46
+ fights: number;
47
+ wins: number;
48
+ losses: number;
49
+ draws: number;
50
+ /** Wins out of decided fights (draws excluded). */
51
+ winRate: RateEstimate;
52
+ }
53
+ export interface FightSummary {
54
+ schemaVersion: typeof FIGHTS_SCHEMA_VERSION;
55
+ fights: number;
56
+ overall: FightBucket;
57
+ byInitiator: FightBucket[];
58
+ byStage: FightBucket[];
59
+ byThirdParty: FightBucket[];
60
+ byDuration: FightBucket[];
61
+ initiatorEdge: DifferenceEstimate;
62
+ thirdPartyEdge: DifferenceEstimate;
63
+ notes: string[];
64
+ }
65
+ export declare function summarizeFights(items: readonly FightRecord[]): FightSummary;
66
+ export interface RoleSummary {
67
+ accountId: string;
68
+ fights: number;
69
+ firstKnocks: number;
70
+ firstDowned: number;
71
+ knocksDealt: number;
72
+ timesDowned: number;
73
+ kills: number;
74
+ revives: number;
75
+ }
76
+ export interface RoleShare {
77
+ accountId: string;
78
+ name: string;
79
+ fights: number;
80
+ firstKnockShare: number | null;
81
+ firstDownedShare: number | null;
82
+ reviveShare: number | null;
83
+ killShare: number | null;
84
+ counts: RoleCounts;
85
+ titles: string[];
86
+ }
87
+ export declare const ROLE_MIN_TOTAL = 15;
88
+ export declare const ROLE_TITLE_SHARE = 0.4;
89
+ export declare function summarizeRoles(items: readonly FightMatchStats[], names: ReadonlyMap<string, string>): RoleShare[];
@@ -0,0 +1,238 @@
1
+ import { newcombeDifference, wilsonInterval, } from './analytics.js';
2
+ import { battlePlayerRef } from './battle.js';
3
+ import { deathStageOf } from './combat-quality.js';
4
+ export const FIGHTS_SCHEMA_VERSION = 1;
5
+ export const FIGHT_MIN_GROUP = 12;
6
+ export function emptyRoleCounts() {
7
+ return emptyRole();
8
+ }
9
+ function emptyRole() {
10
+ return {
11
+ fights: 0,
12
+ initiations: 0,
13
+ firstKnocks: 0,
14
+ firstDowned: 0,
15
+ knocksDealt: 0,
16
+ timesDowned: 0,
17
+ kills: 0,
18
+ revives: 0,
19
+ fightsWithKnock: 0,
20
+ chainsDealt: 0,
21
+ chainsFinished: 0,
22
+ };
23
+ }
24
+ /**
25
+ * Win/lose is decided by unique victims on the observed timeline: a fight is won when more opponents than
26
+ * squad members were knocked or killed. Fights with no knock at all are not scored.
27
+ */
28
+ export function analyzeFights(result, matchId, trackedAccountIds) {
29
+ const stats = { fights: [], roles: {} };
30
+ const accountByRef = new Map(trackedAccountIds.map((accountId) => [battlePlayerRef(matchId, accountId), accountId]));
31
+ const trackedTeams = new Set();
32
+ for (const episode of result.episodes) {
33
+ for (const team of episode.teams)
34
+ if (team.tracked && team.teamRef)
35
+ trackedTeams.add(team.teamRef);
36
+ }
37
+ if (!trackedTeams.size)
38
+ return stats;
39
+ const mine = (ref) => ref !== null && accountByRef.has(ref);
40
+ const role = (ref) => (stats.roles[accountByRef.get(ref)] ??= emptyRole());
41
+ const samples = result.circleSamples
42
+ .filter((sample) => sample.phase === 'in_match' && sample.timeSeconds !== null && sample.aliveTeams !== null)
43
+ .sort((left, right) => left.timeSeconds - right.timeSeconds);
44
+ const aliveTeamsAt = (time) => {
45
+ let found = null;
46
+ for (const sample of samples) {
47
+ if (sample.timeSeconds > time)
48
+ break;
49
+ found = sample.aliveTeams;
50
+ }
51
+ return found;
52
+ };
53
+ for (const group of result.battles) {
54
+ if (!group.teamRefs.some((ref) => trackedTeams.has(ref)))
55
+ continue;
56
+ const events = [...group.timeline].sort((left, right) => left.timeSeconds - right.timeSeconds);
57
+ const won = new Set();
58
+ const lost = new Set();
59
+ const involved = new Set();
60
+ let initiator = 'unknown';
61
+ let firstKnockSeen = false;
62
+ const knockers = new Set();
63
+ for (const event of events) {
64
+ const actorTracked = event.actorTeamRef !== null && trackedTeams.has(event.actorTeamRef);
65
+ const victimTracked = event.victimTeamRef !== null && trackedTeams.has(event.victimTeamRef);
66
+ if (mine(event.actorRef))
67
+ involved.add(event.actorRef);
68
+ if (mine(event.victimRef))
69
+ involved.add(event.victimRef);
70
+ if (initiator === 'unknown' &&
71
+ event.type === 'DAMAGE' &&
72
+ (event.damage ?? 0) > 0 &&
73
+ actorTracked !== victimTracked) {
74
+ initiator = actorTracked ? 'us' : 'them';
75
+ if (actorTracked && mine(event.actorRef))
76
+ role(event.actorRef).initiations += 1;
77
+ }
78
+ if (event.type === 'KNOCK' || event.type === 'KILL') {
79
+ if (event.victimRef && victimTracked && !actorTracked)
80
+ lost.add(event.victimRef);
81
+ else if (event.victimRef && actorTracked && !victimTracked)
82
+ won.add(event.victimRef);
83
+ }
84
+ if (event.type === 'KNOCK') {
85
+ if (!firstKnockSeen) {
86
+ firstKnockSeen = true;
87
+ if (mine(event.actorRef) && !victimTracked)
88
+ role(event.actorRef).firstKnocks += 1;
89
+ if (mine(event.victimRef) && !actorTracked)
90
+ role(event.victimRef).firstDowned += 1;
91
+ }
92
+ if (mine(event.actorRef) && !victimTracked) {
93
+ role(event.actorRef).knocksDealt += 1;
94
+ knockers.add(event.actorRef);
95
+ }
96
+ if (mine(event.victimRef) && !actorTracked)
97
+ role(event.victimRef).timesDowned += 1;
98
+ }
99
+ else if (event.type === 'KILL' && mine(event.actorRef) && !victimTracked) {
100
+ role(event.actorRef).kills += 1;
101
+ }
102
+ else if (event.type === 'REVIVE' && mine(event.actorRef) && victimTracked) {
103
+ role(event.actorRef).revives += 1;
104
+ }
105
+ }
106
+ for (const ref of involved)
107
+ role(ref).fights += 1;
108
+ for (const ref of knockers)
109
+ role(ref).fightsWithKnock += 1;
110
+ if (won.size + lost.size === 0)
111
+ continue;
112
+ const alive = aliveTeamsAt(group.startSeconds);
113
+ stats.fights.push({
114
+ outcome: won.size > lost.size ? 'win' : won.size < lost.size ? 'lose' : 'draw',
115
+ initiator,
116
+ stage: deathStageOf(alive),
117
+ thirdParty: group.possibleThirdParty.length > 0,
118
+ teams: group.teamRefs.length,
119
+ durationSeconds: group.durationSeconds,
120
+ });
121
+ }
122
+ const seenChains = new Set();
123
+ for (const episode of result.episodes) {
124
+ for (const chain of episode.knockChains) {
125
+ if (seenChains.has(chain.knockEventId))
126
+ continue;
127
+ seenChains.add(chain.knockEventId);
128
+ if (!mine(chain.attackerRef) || mine(chain.knockedPlayerRef))
129
+ continue;
130
+ const entry = role(chain.attackerRef);
131
+ entry.chainsDealt += 1;
132
+ if (chain.outcome === 'KILL')
133
+ entry.chainsFinished += 1;
134
+ }
135
+ }
136
+ return stats;
137
+ }
138
+ function bucket(key, label, records) {
139
+ const wins = records.filter((record) => record.outcome === 'win').length;
140
+ const losses = records.filter((record) => record.outcome === 'lose').length;
141
+ return {
142
+ key,
143
+ label,
144
+ fights: records.length,
145
+ wins,
146
+ losses,
147
+ draws: records.length - wins - losses,
148
+ winRate: wilsonInterval(wins, wins + losses),
149
+ };
150
+ }
151
+ export function summarizeFights(items) {
152
+ const group = (key, label, predicate) => bucket(key, label, items.filter(predicate));
153
+ const us = group('us', '我方先手', (record) => record.initiator === 'us');
154
+ const them = group('them', '对方先手', (record) => record.initiator === 'them');
155
+ const withThird = group('third', '有第三方介入', (record) => record.thirdParty);
156
+ const withoutThird = group('duel', '两队对战', (record) => !record.thirdParty);
157
+ return {
158
+ schemaVersion: FIGHTS_SCHEMA_VERSION,
159
+ fights: items.length,
160
+ overall: bucket('all', '全部交战', items),
161
+ byInitiator: [
162
+ us,
163
+ them,
164
+ group('unknown', '先手不明', (record) => record.initiator === 'unknown'),
165
+ ],
166
+ byStage: ['开局', '中期', '后期', '决赛圈', '未知'].map((stage) => group(stage, stage === '未知' ? '阶段未知' : stage, (record) => record.stage === stage)),
167
+ byThirdParty: [withThird, withoutThird],
168
+ byDuration: [
169
+ group('short', '短(<30 秒)', (record) => record.durationSeconds < 30),
170
+ group('medium', '中(30–90 秒)', (record) => record.durationSeconds >= 30 && record.durationSeconds < 90),
171
+ group('long', '长(≥90 秒)', (record) => record.durationSeconds >= 90),
172
+ ],
173
+ initiatorEdge: us.wins + us.losses >= FIGHT_MIN_GROUP && them.wins + them.losses >= FIGHT_MIN_GROUP
174
+ ? newcombeDifference(us.winRate, them.winRate)
175
+ : newcombeDifference(wilsonInterval(0, 0), wilsonInterval(0, 0)),
176
+ thirdPartyEdge: withThird.wins + withThird.losses >= FIGHT_MIN_GROUP &&
177
+ withoutThird.wins + withoutThird.losses >= FIGHT_MIN_GROUP
178
+ ? newcombeDifference(withThird.winRate, withoutThird.winRate)
179
+ : newcombeDifference(wilsonInterval(0, 0), wilsonInterval(0, 0)),
180
+ notes: [
181
+ '胜负按时间线上唯一的被击倒或击杀人数判定:对手被打倒的人数多于我方即为胜,相等为平;没有任何倒地的交战不计入。',
182
+ '先手 = 交战中第一次有效伤害的发起方(先手往往也意味着先发现、占位置,是相关性不是因果);阶段 = 交战开始时场上剩余队伍数;第三方介入只是时间重叠候选,且「有第三方」等价于交战涉及 3 队及以上。',
183
+ ],
184
+ };
185
+ }
186
+ export const ROLE_MIN_TOTAL = 15;
187
+ export const ROLE_TITLE_SHARE = 0.4;
188
+ export function summarizeRoles(items, names) {
189
+ const totals = new Map();
190
+ for (const item of items) {
191
+ for (const [accountId, counts] of Object.entries(item.roles)) {
192
+ const current = totals.get(accountId) ?? emptyRole();
193
+ Object.keys(current).forEach((key) => {
194
+ current[key] += counts[key];
195
+ });
196
+ totals.set(accountId, current);
197
+ }
198
+ }
199
+ const sum = (key) => [...totals.values()].reduce((total, counts) => total + counts[key], 0);
200
+ const totalFirst = sum('firstKnocks');
201
+ const totalDowned = sum('firstDowned');
202
+ const totalRevives = sum('revives');
203
+ const totalKills = sum('kills');
204
+ const share = (value, total) => (total > 0 ? value / total : null);
205
+ return [...totals.entries()]
206
+ .map(([accountId, counts]) => {
207
+ const firstKnockShare = share(counts.firstKnocks, totalFirst);
208
+ const firstDownedShare = share(counts.firstDowned, totalDowned);
209
+ const reviveShare = share(counts.revives, totalRevives);
210
+ const killShare = share(counts.kills, totalKills);
211
+ const titles = [];
212
+ if (totalFirst >= ROLE_MIN_TOTAL &&
213
+ firstKnockShare !== null &&
214
+ firstKnockShare >= ROLE_TITLE_SHARE)
215
+ titles.push('开团手');
216
+ if (totalDowned >= ROLE_MIN_TOTAL &&
217
+ firstDownedShare !== null &&
218
+ firstDownedShare >= ROLE_TITLE_SHARE)
219
+ titles.push('先锋盾(最常被先倒)');
220
+ if (totalRevives >= ROLE_MIN_TOTAL && reviveShare !== null && reviveShare >= ROLE_TITLE_SHARE)
221
+ titles.push('救援担当');
222
+ if (totalKills >= ROLE_MIN_TOTAL && killShare !== null && killShare >= ROLE_TITLE_SHARE)
223
+ titles.push('收割机');
224
+ return {
225
+ accountId,
226
+ name: names.get(accountId) ?? '未知',
227
+ fights: counts.fights,
228
+ firstKnockShare,
229
+ firstDownedShare,
230
+ reviveShare,
231
+ killShare,
232
+ counts,
233
+ titles,
234
+ };
235
+ })
236
+ .sort((left, right) => right.fights - left.fights || left.name.localeCompare(right.name));
237
+ }
238
+ //# sourceMappingURL=fights.js.map