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

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,73 @@
1
+ export declare const MATCH_RELATIVE_SCHEMA_VERSION: 1;
2
+ /** A player needs at least this many other matches before a match is compared with their usual level. */
3
+ export declare const MATCH_RELATIVE_MIN_HISTORY = 8;
4
+ /** Percentiles at or beyond these bounds are called out as unusually good or bad. */
5
+ export declare const MATCH_RELATIVE_NOTABLE = 0.85;
6
+ export type RelativeMetricKey = 'damage' | 'kills' | 'knocks' | 'assists' | 'survival' | 'dpm' | 'hitRate' | 'takedowns' | 'longestStreak' | 'wipeTakedowns' | 'timesDowned';
7
+ export interface RelativeMetricDefinition {
8
+ key: RelativeMetricKey;
9
+ label: string;
10
+ /** How values are shown: whole numbers, minutes, a share, or one decimal. */
11
+ format: 'int' | 'minutes' | 'percent' | 'decimal';
12
+ higherIsBetter: boolean;
13
+ /** Counts toward the player's overall form score for the match. */
14
+ core: boolean;
15
+ }
16
+ export declare const RELATIVE_METRICS: readonly RelativeMetricDefinition[];
17
+ export type RelativeValues = Partial<Record<RelativeMetricKey, number | null>>;
18
+ export interface RelativePlayerInput {
19
+ accountId: string;
20
+ name: string;
21
+ current: RelativeValues;
22
+ /** The same metrics from the player's other matches in scope (the current match excluded). */
23
+ history: readonly RelativeValues[];
24
+ }
25
+ export interface RelativeReading {
26
+ key: RelativeMetricKey;
27
+ label: string;
28
+ format: RelativeMetricDefinition['format'];
29
+ higherIsBetter: boolean;
30
+ value: number | null;
31
+ median: number | null;
32
+ /** Midrank position of this match among the player's history, 0 (lowest) to 1 (highest). */
33
+ percentile: number | null;
34
+ /** Percentile oriented so 1 is always good (flipped for metrics where lower is better). */
35
+ goodness: number | null;
36
+ historyCount: number;
37
+ }
38
+ export interface RelativePlayer {
39
+ accountId: string;
40
+ name: string;
41
+ historyMatches: number;
42
+ metrics: RelativeReading[];
43
+ /** Mean goodness over the core metrics; 0.5 is an ordinary match for this player. */
44
+ form: number | null;
45
+ }
46
+ export interface MatchRelative {
47
+ schemaVersion: typeof MATCH_RELATIVE_SCHEMA_VERSION;
48
+ players: RelativePlayer[];
49
+ /** Placement percentile among the squad's matches in scope (1 = best placement seen). */
50
+ placement: {
51
+ value: number | null;
52
+ goodness: number | null;
53
+ historyCount: number;
54
+ };
55
+ highlights: Array<{
56
+ tone: 'good' | 'bad' | 'info';
57
+ text: string;
58
+ }>;
59
+ notes: string[];
60
+ }
61
+ export declare function formatRelativeValue(format: RelativeMetricDefinition['format'], value: number): string;
62
+ /**
63
+ * Places one match against each member's own history: every metric becomes a percentile among that member's
64
+ * other matches, so "612 damage" reads as "better than 93% of your matches". Members with too short a history
65
+ * get no percentiles; nothing is compared across members, so a support player is not judged by a fragger's bar.
66
+ */
67
+ export declare function buildMatchRelative(input: {
68
+ players: readonly RelativePlayerInput[];
69
+ placement: {
70
+ current: number | null;
71
+ history: readonly number[];
72
+ };
73
+ }): MatchRelative;
@@ -0,0 +1,145 @@
1
+ export const MATCH_RELATIVE_SCHEMA_VERSION = 1;
2
+ /** A player needs at least this many other matches before a match is compared with their usual level. */
3
+ export const MATCH_RELATIVE_MIN_HISTORY = 8;
4
+ /** Percentiles at or beyond these bounds are called out as unusually good or bad. */
5
+ export const MATCH_RELATIVE_NOTABLE = 0.85;
6
+ export const RELATIVE_METRICS = [
7
+ { key: 'damage', label: '伤害', format: 'int', higherIsBetter: true, core: true },
8
+ { key: 'kills', label: '击杀', format: 'int', higherIsBetter: true, core: true },
9
+ { key: 'knocks', label: '击倒', format: 'int', higherIsBetter: true, core: true },
10
+ { key: 'assists', label: '助攻', format: 'int', higherIsBetter: true, core: false },
11
+ { key: 'survival', label: '存活', format: 'minutes', higherIsBetter: true, core: true },
12
+ { key: 'dpm', label: '每分钟伤害', format: 'decimal', higherIsBetter: true, core: true },
13
+ { key: 'hitRate', label: '命中率', format: 'percent', higherIsBetter: true, core: false },
14
+ { key: 'takedowns', label: '放倒', format: 'int', higherIsBetter: true, core: false },
15
+ { key: 'longestStreak', label: '最长连杀', format: 'int', higherIsBetter: true, core: false },
16
+ { key: 'wipeTakedowns', label: '灭队中放倒', format: 'int', higherIsBetter: true, core: false },
17
+ { key: 'timesDowned', label: '被击倒', format: 'int', higherIsBetter: false, core: false },
18
+ ];
19
+ function midrank(values, value) {
20
+ const less = values.filter((other) => other < value).length;
21
+ const equal = values.filter((other) => other === value).length;
22
+ return (less + 0.5 * equal) / values.length;
23
+ }
24
+ function median(values) {
25
+ if (!values.length)
26
+ return null;
27
+ const sorted = [...values].sort((left, right) => left - right);
28
+ const mid = Math.floor(sorted.length / 2);
29
+ return sorted.length % 2 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2;
30
+ }
31
+ export function formatRelativeValue(format, value) {
32
+ if (format === 'percent')
33
+ return `${(value * 100).toFixed(1)}%`;
34
+ if (format === 'minutes')
35
+ return `${value.toFixed(1)} 分钟`;
36
+ if (format === 'decimal')
37
+ return value.toFixed(1);
38
+ return String(Math.round(value));
39
+ }
40
+ /**
41
+ * Places one match against each member's own history: every metric becomes a percentile among that member's
42
+ * other matches, so "612 damage" reads as "better than 93% of your matches". Members with too short a history
43
+ * get no percentiles; nothing is compared across members, so a support player is not judged by a fragger's bar.
44
+ */
45
+ export function buildMatchRelative(input) {
46
+ const players = input.players.map((player) => {
47
+ const metrics = RELATIVE_METRICS.map((definition) => {
48
+ const value = player.current[definition.key] ?? null;
49
+ const history = player.history
50
+ .map((values) => values[definition.key])
51
+ .filter((item) => typeof item === 'number' && Number.isFinite(item));
52
+ const enough = history.length >= MATCH_RELATIVE_MIN_HISTORY;
53
+ const percentile = value !== null && enough ? midrank(history, value) : null;
54
+ return {
55
+ key: definition.key,
56
+ label: definition.label,
57
+ format: definition.format,
58
+ higherIsBetter: definition.higherIsBetter,
59
+ value,
60
+ median: enough ? median(history) : null,
61
+ percentile,
62
+ goodness: percentile === null ? null : definition.higherIsBetter ? percentile : 1 - percentile,
63
+ historyCount: history.length,
64
+ };
65
+ });
66
+ const core = metrics.filter((reading) => RELATIVE_METRICS.find((definition) => definition.key === reading.key).core &&
67
+ reading.goodness !== null);
68
+ return {
69
+ accountId: player.accountId,
70
+ name: player.name,
71
+ historyMatches: player.history.length,
72
+ metrics,
73
+ form: core.length >= 3
74
+ ? core.reduce((sum, reading) => sum + reading.goodness, 0) / core.length
75
+ : null,
76
+ };
77
+ });
78
+ const placementHistory = input.placement.history.filter((value) => Number.isFinite(value) && value > 0);
79
+ const placementEnough = input.placement.current !== null && placementHistory.length >= MATCH_RELATIVE_MIN_HISTORY;
80
+ // Lower placement numbers are better, so goodness is the share of past matches that placed worse.
81
+ const placementGoodness = placementEnough
82
+ ? 1 - midrank(placementHistory, input.placement.current)
83
+ : null;
84
+ const highlights = [];
85
+ if (placementGoodness !== null) {
86
+ if (placementGoodness >= MATCH_RELATIVE_NOTABLE)
87
+ highlights.push({
88
+ tone: 'good',
89
+ text: `第 ${input.placement.current} 名,好于小队 ${(placementGoodness * 100).toFixed(0)}% 的比赛。`,
90
+ });
91
+ else if (placementGoodness <= 1 - MATCH_RELATIVE_NOTABLE)
92
+ highlights.push({
93
+ tone: 'bad',
94
+ text: `第 ${input.placement.current} 名,差于小队 ${((1 - placementGoodness) * 100).toFixed(0)}% 的比赛。`,
95
+ });
96
+ }
97
+ const ranked = players
98
+ .filter((player) => player.form !== null)
99
+ .sort((left, right) => right.form - left.form);
100
+ if (ranked.length >= 2 && ranked[0].form >= 0.65)
101
+ highlights.push({
102
+ tone: 'good',
103
+ text: `本局状态最好:${ranked[0].name}(综合分位 ${(ranked[0].form * 100).toFixed(0)},0.5 为他的平常水平)。`,
104
+ });
105
+ const notable = players
106
+ .flatMap((player) => player.metrics
107
+ .filter((reading) => reading.goodness !== null &&
108
+ reading.value !== null &&
109
+ reading.median !== null &&
110
+ (reading.goodness >= MATCH_RELATIVE_NOTABLE ||
111
+ reading.goodness <= 1 - MATCH_RELATIVE_NOTABLE) &&
112
+ // A zero against a zero median is not news.
113
+ !(reading.value === 0 && reading.median === 0))
114
+ .map((reading) => ({ player, reading, strength: Math.abs(reading.goodness - 0.5) })))
115
+ .sort((left, right) => right.strength - left.strength || left.player.name.localeCompare(right.player.name));
116
+ const perPlayer = new Map();
117
+ for (const { player, reading } of notable) {
118
+ if ((perPlayer.get(player.accountId) ?? 0) >= 2 || highlights.length >= 7)
119
+ continue;
120
+ perPlayer.set(player.accountId, (perPlayer.get(player.accountId) ?? 0) + 1);
121
+ const good = reading.goodness >= 0.5;
122
+ const share = Math.round((good ? reading.goodness : 1 - reading.goodness) * 100);
123
+ highlights.push({
124
+ tone: good ? 'good' : 'bad',
125
+ text: `${player.name} ${reading.label} ${formatRelativeValue(reading.format, reading.value)},${good ? '好于' : '差于'}他 ${share}% 的比赛(平时中位 ${formatRelativeValue(reading.format, reading.median)})。`,
126
+ });
127
+ }
128
+ if (!highlights.length && players.some((player) => player.form !== null))
129
+ highlights.push({ tone: 'info', text: '这一局各项数据都在每个人的平常范围内。' });
130
+ return {
131
+ schemaVersion: MATCH_RELATIVE_SCHEMA_VERSION,
132
+ players,
133
+ placement: {
134
+ value: input.placement.current,
135
+ goodness: placementGoodness,
136
+ historyCount: placementHistory.length,
137
+ },
138
+ highlights,
139
+ notes: [
140
+ `分位 = 本局数值在该队员其他比赛里的位置(只和自己比),0.5 为平常水平;至少 ${MATCH_RELATIVE_MIN_HISTORY} 场历史才计算。`,
141
+ '综合分位 = 伤害、击杀、击倒、存活、每分钟伤害五项分位的平均。命中率、放倒、连杀、灭队只在有归档遥测的比赛里可用。',
142
+ ],
143
+ };
144
+ }
145
+ //# sourceMappingURL=match-relative.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"match-relative.js","sourceRoot":"","sources":["../../src/match-relative.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,155 @@
1
+ import { type MatchRotation, type StoryDown, type TurningPoint } from './match-insight.js';
2
+ export declare const MATCH_STORY_SCHEMA_VERSION: 1;
3
+ /**
4
+ * A fight stays open while the squad keeps making contact with one of its opposing teams at most this far apart, and
5
+ * a contact with a team that is not yet in any open fight joins one only when it comes within STORY_FIGHT_JOIN_SECONDS
6
+ * of that fight's latest contact (a third party arriving mid-fight); otherwise it starts a separate fight. Grouping by
7
+ * time alone chained back-to-back fights against different teams into one fight of several minutes.
8
+ */
9
+ export declare const STORY_FIGHT_GAP_SECONDS = 35;
10
+ export declare const STORY_FIGHT_JOIN_SECONDS = 12;
11
+ /** Route samples closer together than this are dropped to keep the payload small. */
12
+ export declare const STORY_ROUTE_STEP_SECONDS = 10;
13
+ /** Opposing players are sampled less often than the squad to keep the payload small. */
14
+ export declare const STORY_ENEMY_STEP_SECONDS = 20;
15
+ export type StoryEventKind = 'knock' | 'kill' | 'downed' | 'death' | 'revive';
16
+ export interface StoryEvent {
17
+ t: number;
18
+ kind: StoryEventKind;
19
+ /** Squad member the event is about (the one who knocked, killed, was downed, died or revived). */
20
+ member: string;
21
+ /** The other side's squad member for a revive; null for enemies. */
22
+ other: string | null;
23
+ weapon: string | null;
24
+ meters: number | null;
25
+ /** True when the opponent was a player (false for the blue zone, falls and the like). */
26
+ byPlayer: boolean;
27
+ /** A kill that finished a player who was already knocked, or a death that followed a knock. */
28
+ afterKnock: boolean;
29
+ }
30
+ export type LifeState = 'alive' | 'down' | 'dead';
31
+ export interface LifeSegment {
32
+ from: number;
33
+ to: number;
34
+ state: LifeState;
35
+ }
36
+ export interface StoryRoutePoint {
37
+ t: number;
38
+ x: number;
39
+ y: number;
40
+ vehicle: boolean;
41
+ down: boolean;
42
+ /** Present (true) only while the player is standing in the circle's gas. */
43
+ blue?: boolean;
44
+ }
45
+ export interface StoryCirclePoint {
46
+ t: number;
47
+ phase: number | null;
48
+ aliveTeams: number | null;
49
+ alivePlayers: number | null;
50
+ /** Safe zone and warning zone in metres. */
51
+ zone: {
52
+ x: number;
53
+ y: number;
54
+ r: number;
55
+ } | null;
56
+ warn: {
57
+ x: number;
58
+ y: number;
59
+ r: number;
60
+ } | null;
61
+ }
62
+ /** One opposing player's route, thinned to a sample every STORY_ENEMY_STEP_SECONDS: [t, x, y] in seconds and metres. */
63
+ export interface StoryEnemy {
64
+ id: string;
65
+ team: string;
66
+ route: Array<[number, number, number]>;
67
+ /** When the player was knocked out of the match for good, null when they survived. */
68
+ diedAt: number | null;
69
+ }
70
+ /** An opposing team's part in one fight. */
71
+ export interface StoryFightEnemy {
72
+ team: string;
73
+ /** Players of that team seen in the match. */
74
+ size: number;
75
+ /** Players of that team our squad took down in this fight. */
76
+ downed: number;
77
+ damageToUs: number;
78
+ damageFromUs: number;
79
+ /** Weapons they hurt us with, most damage first. */
80
+ weapons: string[];
81
+ }
82
+ export interface StoryFight {
83
+ index: number;
84
+ start: number;
85
+ end: number;
86
+ phase: number | null;
87
+ aliveTeams: number | null;
88
+ enemyTeams: number;
89
+ outcome: 'win' | 'lose' | 'draw' | 'skirmish';
90
+ initiator: 'us' | 'them' | 'unknown';
91
+ enemyDowns: number;
92
+ ourDowns: number;
93
+ ourKills: number;
94
+ revives: number;
95
+ damageDealt: number;
96
+ damageTaken: number;
97
+ /** Members involved with their share of the damage dealt in the fight. */
98
+ members: Array<{
99
+ member: string;
100
+ damage: number;
101
+ knocks: number;
102
+ downed: number;
103
+ }>;
104
+ enemies: StoryFightEnemy[];
105
+ }
106
+ export interface StoryPlayer {
107
+ member: string;
108
+ landing: {
109
+ t: number;
110
+ x: number;
111
+ y: number;
112
+ } | null;
113
+ lifeline: LifeSegment[];
114
+ route: StoryRoutePoint[];
115
+ /** Squad-member damage dealt to opponents in one-minute bins. */
116
+ damagePerMinute: number[];
117
+ /** Time of the squad member's death, null when alive at the end. */
118
+ diedAt: number | null;
119
+ }
120
+ export interface MatchStory {
121
+ schemaVersion: typeof MATCH_STORY_SCHEMA_VERSION;
122
+ mapName: string | null;
123
+ durationSeconds: number;
124
+ startTeams: number | null;
125
+ startPlayers: number | null;
126
+ events: StoryEvent[];
127
+ fights: StoryFight[];
128
+ circle: StoryCirclePoint[];
129
+ /** First time each circle phase is seen. */
130
+ phaseStarts: Array<{
131
+ phase: number;
132
+ t: number;
133
+ }>;
134
+ players: StoryPlayer[];
135
+ /** Every other player's thinned route, so the map can show who was where. */
136
+ enemies: StoryEnemy[];
137
+ /** Why each squad knock-down happened, from the fire the squad took and returned just before it. */
138
+ downs: StoryDown[];
139
+ /** The few moments that decided the match. */
140
+ turningPoints: TurningPoint[];
141
+ /** How the squad moved with the circle. */
142
+ rotation: MatchRotation;
143
+ /** When the last squad member went down for good, null when the squad survived to the end. */
144
+ squadOutAt: number | null;
145
+ /** Teams still alive when the squad was out (or at the end), and the squad's final position among them. */
146
+ teamsAtEnd: number | null;
147
+ }
148
+ /**
149
+ * Turns one raw telemetry payload into a readable story of the squad's match: who went down and when, every
150
+ * fight in plain terms, the route on the map against the circle, and how fast damage was dealt. Times are seconds
151
+ * from the match start (LogMatchStart).
152
+ */
153
+ export declare function analyzeMatchStory(payload: unknown, trackedIds: ReadonlySet<string>): MatchStory;
154
+ /** Display label for a fight result. */
155
+ export declare function fightOutcomeText(outcome: StoryFight['outcome']): string;