@cs2dak/contract 0.2.1 → 1.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.
- package/LICENSE +7 -0
- package/package.json +3 -3
- package/src/analysis.ts +228 -0
- package/src/bracket.ts +68 -0
- package/src/cohort.ts +60 -0
- package/src/contract.test.ts +624 -0
- package/src/demo-package.ts +38 -0
- package/src/dependency-boundaries.test.ts +27 -0
- package/src/duel.ts +105 -0
- package/src/event-package.test.ts +48 -0
- package/src/event-package.ts +163 -0
- package/src/evidence.ts +22 -0
- package/src/index.ts +20 -599
- package/src/leaderboard.ts +102 -0
- package/src/map-intelligence.ts +202 -0
- package/src/map-role.ts +317 -0
- package/src/player.ts +107 -0
- package/src/qa.ts +21 -0
- package/src/radar-field.ts +79 -0
- package/src/scoring.ts +87 -0
- package/src/series.ts +77 -0
- package/src/team.ts +72 -0
- package/src/trails.ts +56 -0
- package/src/upstream.ts +110 -0
- package/src/veto.ts +44 -0
- package/src/workspace.ts +341 -0
package/src/player.ts
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { teamKeySchema } from "cs2-demo-format";
|
|
3
|
+
import { accountContextAvailabilitySchema } from "./analysis.js";
|
|
4
|
+
import { leaderboardMetricKeySchema } from "./leaderboard.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 选手赛季档案。由 @cs2dak/presentation 从 SeasonCohortBundle 中单个选手派生。
|
|
8
|
+
* 产品中立:身份用 playerKey/steamIds/externalUserId,不含数据库/路由/权限语义(规则 8)。
|
|
9
|
+
* 不算评分公式;rating/style 均来自 cohort 已接线的 rival-rating 结果(规则 5)。
|
|
10
|
+
* 缺失保持 null(规则 6);style 在 PRISM 缺失时整体为 null。
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export const rrBreakdownEntrySchema = z.object({
|
|
14
|
+
key: z.enum(["combat", "trade", "mapControl", "clutch", "objective", "utility"]),
|
|
15
|
+
label: z.string(),
|
|
16
|
+
value: z.number()
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
/** PRISM 单根轴的展示项:行为倾向与执行效率分离,避免把角色、频率和效果混成一个分数。 */
|
|
20
|
+
export const playerStyleAxisSchema = z.object({
|
|
21
|
+
key: z.string(), // PrismAxisKey
|
|
22
|
+
label: z.string(),
|
|
23
|
+
/** 该轴在当前 cohort 内的行为参与度相对位置。 */
|
|
24
|
+
involvementPercentile: z.number().min(0).max(100).nullable(),
|
|
25
|
+
/** 该轴在当前 cohort 内的执行效率相对位置。 */
|
|
26
|
+
efficiencyPercentile: z.number().min(0).max(100).nullable(),
|
|
27
|
+
/** 上游融合值,仅供解释/导出,不再用作雷达形状。 */
|
|
28
|
+
combinedPercentile: z.number().min(0).max(100).nullable(),
|
|
29
|
+
/** 上游配置中可用信号权重占比,0–1。 */
|
|
30
|
+
signalCoverage: z.number().min(0).max(1),
|
|
31
|
+
/** 可供当前 cohort 排名的有效选手数;不足 5 人时不展示精确 P 值。 */
|
|
32
|
+
comparisonCount: z.number().int().nonnegative(),
|
|
33
|
+
status: z.enum(["ready", "partial", "unavailable", "insufficient"])
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
export const playerStyleSchema = z.object({
|
|
37
|
+
weightsVersion: z.string(),
|
|
38
|
+
/** 整体着色依据:RR 百分位(0–100)。 */
|
|
39
|
+
rrPercentile: z.number().min(0).max(100),
|
|
40
|
+
/** 八根轴,按 PRISM_AXIS_ORDER 排序。 */
|
|
41
|
+
axes: z.array(playerStyleAxisSchema)
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
export const playerSeasonTrendPointSchema = z.object({
|
|
45
|
+
matchId: z.string(),
|
|
46
|
+
rivalhubRR: z.number().nonnegative(),
|
|
47
|
+
hltvRating: z.number().nonnegative()
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
export const playerWeaponProfileEntrySchema = z.object({
|
|
51
|
+
weapon: z.string(),
|
|
52
|
+
label: z.string(),
|
|
53
|
+
kills: z.number().int().nonnegative(),
|
|
54
|
+
killSharePercent: z.number().min(0).max(100),
|
|
55
|
+
headshotPercent: z.number().min(0).max(100).nullable(),
|
|
56
|
+
tradeKillPercent: z.number().min(0).max(100).nullable(),
|
|
57
|
+
noScopePercent: z.number().min(0).max(100).nullable(),
|
|
58
|
+
throughSmokePercent: z.number().min(0).max(100).nullable(),
|
|
59
|
+
wallbangPercent: z.number().min(0).max(100).nullable(),
|
|
60
|
+
averagePenetratedObjects: z.number().nonnegative().nullable()
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
export const playerSeasonProfileSchema = z.object({
|
|
64
|
+
/** 0.2:PRISM 轴拆分为行为倾向、执行效率,并附带信号/样本可用性。 */
|
|
65
|
+
version: z.literal("cs2-demo-analysis-kit/player-profile-0.2"),
|
|
66
|
+
weightsVersion: z.string(),
|
|
67
|
+
playerKey: z.string(),
|
|
68
|
+
name: z.string(),
|
|
69
|
+
steamIds: z.array(z.string()),
|
|
70
|
+
externalUserId: z.string().nullable(),
|
|
71
|
+
teamKeys: z.array(teamKeySchema),
|
|
72
|
+
mapCount: z.number().int().positive(),
|
|
73
|
+
confidence: z.number().min(0).max(1),
|
|
74
|
+
accountContextStatus: z.object({
|
|
75
|
+
buyDelta: accountContextAvailabilitySchema,
|
|
76
|
+
manState: accountContextAvailabilitySchema
|
|
77
|
+
}),
|
|
78
|
+
rating: z.object({
|
|
79
|
+
rivalhubRR: z.number().nonnegative(),
|
|
80
|
+
rivalhubRRRaw: z.number(),
|
|
81
|
+
hltvRating: z.number().nonnegative(),
|
|
82
|
+
hltvPercentile: z.number().min(0).max(100),
|
|
83
|
+
breakdown: z.array(rrBreakdownEntrySchema)
|
|
84
|
+
}),
|
|
85
|
+
/** 与排行榜同口径的展示指标(共享 SEASON_STAT_VIEWS 渲染列)。 */
|
|
86
|
+
metrics: z.record(leaderboardMetricKeySchema, z.number().nullable()),
|
|
87
|
+
weapons: z.array(playerWeaponProfileEntrySchema),
|
|
88
|
+
highlights: z.object({
|
|
89
|
+
wallbangKills: z.number().int().nonnegative().nullable(),
|
|
90
|
+
noScopeKills: z.number().int().nonnegative().nullable(),
|
|
91
|
+
throughSmokeKills: z.number().int().nonnegative().nullable(),
|
|
92
|
+
collateralKills: z.number().int().nonnegative().nullable()
|
|
93
|
+
}),
|
|
94
|
+
style: playerStyleSchema.nullable(),
|
|
95
|
+
/** 每场 RR 趋势,按 matchId 升序。 */
|
|
96
|
+
perMatch: z.array(playerSeasonTrendPointSchema),
|
|
97
|
+
/** 相对赛季 cohort 的强项 / 弱项(按技能类指标的 cohort 百分位派生)。 */
|
|
98
|
+
strengths: z.array(z.string()),
|
|
99
|
+
weaknesses: z.array(z.string())
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
export type RRBreakdownEntry = z.infer<typeof rrBreakdownEntrySchema>;
|
|
103
|
+
export type PlayerStyleAxis = z.infer<typeof playerStyleAxisSchema>;
|
|
104
|
+
export type PlayerStyle = z.infer<typeof playerStyleSchema>;
|
|
105
|
+
export type PlayerSeasonTrendPoint = z.infer<typeof playerSeasonTrendPointSchema>;
|
|
106
|
+
export type PlayerWeaponProfileEntry = z.infer<typeof playerWeaponProfileEntrySchema>;
|
|
107
|
+
export type PlayerSeasonProfile = z.infer<typeof playerSeasonProfileSchema>;
|
package/src/qa.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
export const qaIssueSchema = z.object({
|
|
4
|
+
severity: z.enum(["info", "warning", "error"]),
|
|
5
|
+
code: z.string().min(1),
|
|
6
|
+
message: z.string().min(1),
|
|
7
|
+
path: z.string().optional()
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
export const qaReportSchema = z.object({
|
|
11
|
+
ok: z.boolean(),
|
|
12
|
+
summary: z.object({
|
|
13
|
+
issueCount: z.number().int().nonnegative(),
|
|
14
|
+
errorCount: z.number().int().nonnegative(),
|
|
15
|
+
warningCount: z.number().int().nonnegative()
|
|
16
|
+
}),
|
|
17
|
+
issues: z.array(qaIssueSchema)
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
export type QaIssue = z.infer<typeof qaIssueSchema>;
|
|
21
|
+
export type QaReport = z.infer<typeof qaReportSchema>;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RadarField —— 雷达覆盖场合同(描述性地图控制原语)。
|
|
3
|
+
*
|
|
4
|
+
* 「基础场」逐秒 × 逐格存原始计数(不存概率、不存合成视图),因为原始计数是
|
|
5
|
+
* 加性的:合并两个 scope = 四场逐元素相加 + denom 相加。这一条承重墙撑起 per-match
|
|
6
|
+
* 缓存、scope 聚合、队伍−联赛差分、未来价值学习——全是廉价派生。
|
|
7
|
+
*
|
|
8
|
+
* 归一化(count / denom = 该秒该格被看到/占据/听到的采样占比)、信息差分(tVis−ctVis)、
|
|
9
|
+
* 对拼线(min(tVis,ctVis))、推进波前都在渲染期合成,底层场不污染。
|
|
10
|
+
*
|
|
11
|
+
* 这是 TS 侧计算产物,不跨 Python↔TS 的 v3 ZIP seam,故用手写 interface(非 Zod)。
|
|
12
|
+
* 字段列用 Int32Array:结构化克隆(worker postMessage / IndexedDB)下紧凑且无损。
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* 基础场名。
|
|
17
|
+
* Vis = 4:3 屏幕可见(矩形视锥 ∩ LOS ∩ 烟);Aim = 准星/注意力覆盖(30°圆锥 ∩ LOS ∩ 烟);
|
|
18
|
+
* Pres = 位置占据(最近 nav 格,无 LOS);Sound = 在该格发声会被对面听到的风险(800u 半径)。
|
|
19
|
+
*/
|
|
20
|
+
export type RadarFieldBase =
|
|
21
|
+
| "ctVis" | "tVis"
|
|
22
|
+
| "ctAim" | "tAim"
|
|
23
|
+
| "ctPres" | "tPres"
|
|
24
|
+
| "ctSound" | "tSound";
|
|
25
|
+
|
|
26
|
+
export const RADAR_FIELD_BASES: readonly RadarFieldBase[] = [
|
|
27
|
+
"ctVis", "tVis",
|
|
28
|
+
"ctAim", "tAim",
|
|
29
|
+
"ctPres", "tPres",
|
|
30
|
+
"ctSound", "tSound",
|
|
31
|
+
] as const;
|
|
32
|
+
|
|
33
|
+
export interface RadarFieldGrid {
|
|
34
|
+
/** 规则栅格边长(世界单位)。 */
|
|
35
|
+
cellSize: number;
|
|
36
|
+
/** 每格代表世界坐标 [x, y, z](z 已含靶高),与 fields 列同序。 */
|
|
37
|
+
cells: Array<[number, number, number]>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface RadarFieldScope {
|
|
41
|
+
/** league = 赛事/当前范围基线;team = 单队(identity 归并后的显示名)。 */
|
|
42
|
+
kind: "league" | "team";
|
|
43
|
+
team: string | null;
|
|
44
|
+
/** gun = 长枪局(双方 full/conversion);all = 全部回合。 */
|
|
45
|
+
economy: "gun" | "all";
|
|
46
|
+
/** 贡献的回合总数(含全部采样秒之前的回合计数)。 */
|
|
47
|
+
roundCount: number;
|
|
48
|
+
matchIds: string[];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface RadarField {
|
|
52
|
+
/** 合同结构版本。 */
|
|
53
|
+
schemaVersion: number;
|
|
54
|
+
/** 算法参数指纹(锥角/采样率/格大小/眼高变了即 +1);驱动缓存失效。 */
|
|
55
|
+
computeVersion: number;
|
|
56
|
+
mapName: string;
|
|
57
|
+
/** world→radar 标定版本;变了不可与旧场合并/差分。 */
|
|
58
|
+
calibrationVersion: string;
|
|
59
|
+
/** full = 这批 LOS 射线可信(有 .tri);none = 只有锥+烟,无墙体遮挡,不可与 full 互比。 */
|
|
60
|
+
triAvailability: "full" | "none";
|
|
61
|
+
scope: RadarFieldScope;
|
|
62
|
+
grid: RadarFieldGrid;
|
|
63
|
+
/** 采样秒上界(一回合 1:55 = 115s)。 */
|
|
64
|
+
maxSec: number;
|
|
65
|
+
/**
|
|
66
|
+
* 归一化分母,按 source side 分。当前计算每秒采样一次:
|
|
67
|
+
* denomCt[sec] = scope 内、CT 侧、到达该秒的 side-frame 数;ct* 基础场除以它。
|
|
68
|
+
* denomT 同理给 t* 基础场。
|
|
69
|
+
*/
|
|
70
|
+
denomCt: Int32Array;
|
|
71
|
+
denomT: Int32Array;
|
|
72
|
+
/** fields[base][sec] = Int32Array(cells.length) 原始计数。 */
|
|
73
|
+
fields: Record<RadarFieldBase, Int32Array[]>;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export const RADAR_FIELD_SCHEMA_VERSION = 1;
|
|
77
|
+
|
|
78
|
+
/** 一回合 1:55 = 115s(freeze 后逐秒采样窗,含残局/换防段;已实证)。 */
|
|
79
|
+
export const RADAR_FIELD_MAX_SEC = 115;
|
package/src/scoring.ts
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type { RRIndicators } from "@rivalhub/rival-rating";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
|
|
4
|
+
const clutchSplitSchema = z.object({
|
|
5
|
+
count: z.number().int().nonnegative(),
|
|
6
|
+
won: z.number().int().nonnegative()
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
export const rrIndicatorsSchema: z.ZodType<RRIndicators> = z.object({
|
|
10
|
+
steamId64: z.string(),
|
|
11
|
+
totalRounds: z.number().int().nonnegative(),
|
|
12
|
+
kills: z.number().int().nonnegative(),
|
|
13
|
+
deaths: z.number().int().nonnegative(),
|
|
14
|
+
assists: z.number().int().nonnegative(),
|
|
15
|
+
kpr: z.number().nonnegative(),
|
|
16
|
+
dpr: z.number().nonnegative(),
|
|
17
|
+
apr: z.number().nonnegative(),
|
|
18
|
+
adr: z.number().nonnegative(),
|
|
19
|
+
hsPercent: z.number().min(0).max(100),
|
|
20
|
+
kast: z.number().min(0).max(100),
|
|
21
|
+
survivalRate: z.number().min(0).max(1),
|
|
22
|
+
twoKillRounds: z.number().int().nonnegative(),
|
|
23
|
+
threeKillRounds: z.number().int().nonnegative(),
|
|
24
|
+
fourKillRounds: z.number().int().nonnegative(),
|
|
25
|
+
fiveKillRounds: z.number().int().nonnegative(),
|
|
26
|
+
multiKillRate: z.number().min(0).max(1),
|
|
27
|
+
firstKillCount: z.number().int().nonnegative(),
|
|
28
|
+
firstDeathCount: z.number().int().nonnegative(),
|
|
29
|
+
firstKillRate: z.number().nonnegative(),
|
|
30
|
+
firstDeathRate: z.number().nonnegative(),
|
|
31
|
+
openingDuelRate: z.number().nonnegative(),
|
|
32
|
+
openingDuelWinRate: z.number().min(0).max(1),
|
|
33
|
+
tradeKillCount: z.number().int().nonnegative(),
|
|
34
|
+
tradeDeathCount: z.number().int().nonnegative(),
|
|
35
|
+
tradeKillRate: z.number().nonnegative(),
|
|
36
|
+
tradeDeathRate: z.number().nonnegative(),
|
|
37
|
+
clutchAttempts: z.number().int().nonnegative(),
|
|
38
|
+
clutchWins: z.number().int().nonnegative(),
|
|
39
|
+
clutchWinRate: z.number().min(0).max(1),
|
|
40
|
+
clutchFrequency: z.number().nonnegative(),
|
|
41
|
+
clutchScore: z.number().nonnegative(),
|
|
42
|
+
clutchScoreRate: z.number().nonnegative(),
|
|
43
|
+
vsOne: clutchSplitSchema,
|
|
44
|
+
vsTwo: clutchSplitSchema,
|
|
45
|
+
vsThree: clutchSplitSchema,
|
|
46
|
+
vsFour: clutchSplitSchema,
|
|
47
|
+
vsFive: clutchSplitSchema,
|
|
48
|
+
awpKills: z.number().int().nonnegative(),
|
|
49
|
+
awpKillsPerRound: z.number().nonnegative(),
|
|
50
|
+
awpKillRate: z.number().min(0).max(1),
|
|
51
|
+
sniperKills: z.number().int().nonnegative(),
|
|
52
|
+
sniperKillRate: z.number().min(0).max(1),
|
|
53
|
+
awpMultiKillRate: z.number().min(0).max(1).nullable(),
|
|
54
|
+
awpDuelWinRate: z.number().min(0).max(1).nullable(),
|
|
55
|
+
utilityDamage: z.number().int().nonnegative(),
|
|
56
|
+
utilityDamagePerRound: z.number().nonnegative(),
|
|
57
|
+
flashAssistCount: z.number().int().nonnegative(),
|
|
58
|
+
flashAssistPerRound: z.number().nonnegative(),
|
|
59
|
+
blindDurationTotal: z.number().nonnegative(),
|
|
60
|
+
blindDurationPerRound: z.number().nonnegative(),
|
|
61
|
+
enemyFlashDurationSeconds: z.number().nonnegative().nullable(),
|
|
62
|
+
enemyFlashDurationPerRound: z.number().nonnegative().nullable(),
|
|
63
|
+
teamFlashDurationSeconds: z.number().nonnegative().nullable(),
|
|
64
|
+
teamFlashDurationPerRound: z.number().nonnegative().nullable(),
|
|
65
|
+
grenadeCount: z.number().int().nonnegative(),
|
|
66
|
+
grenadeCountPerRound: z.number().nonnegative(),
|
|
67
|
+
ecoRoundCount: z.number().int().nonnegative(),
|
|
68
|
+
forceRoundCount: z.number().int().nonnegative(),
|
|
69
|
+
fullBuyRoundCount: z.number().int().nonnegative(),
|
|
70
|
+
pistolRoundCount: z.number().int().nonnegative(),
|
|
71
|
+
avgEquipmentValue: z.number().nonnegative(),
|
|
72
|
+
combatDeathCount: z.number().int().nonnegative().nullable(),
|
|
73
|
+
bombDeathCount: z.number().int().nonnegative().nullable(),
|
|
74
|
+
wallbangKillCount: z.number().int().nonnegative().nullable(),
|
|
75
|
+
roundSwingTotal: z.number().nullable(),
|
|
76
|
+
roundSwingPerKill: z.number().nullable()
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
export type {
|
|
80
|
+
CohortAccountResult,
|
|
81
|
+
PrismResult,
|
|
82
|
+
RRIndicators,
|
|
83
|
+
RRResult,
|
|
84
|
+
RRSignals,
|
|
85
|
+
RRSixAccountResult,
|
|
86
|
+
RRSixAccountWeights
|
|
87
|
+
} from "@rivalhub/rival-rating";
|
package/src/series.ts
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { matchWorkspaceModelSchema } from "./workspace.js";
|
|
3
|
+
|
|
4
|
+
export const mvpCandidateSchema = z.object({
|
|
5
|
+
playerKey: z.string(),
|
|
6
|
+
name: z.string(),
|
|
7
|
+
teamName: z.string(),
|
|
8
|
+
recommendationScore: z.number().nonnegative(),
|
|
9
|
+
rivalhubRR: z.number().nonnegative(),
|
|
10
|
+
hltvRating: z.number().nonnegative(),
|
|
11
|
+
confidence: z.number().min(0).max(1),
|
|
12
|
+
explanation: z.array(z.string())
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
export const mvpRecommendationSchema = z.object({
|
|
16
|
+
version: z.literal("cs2-demo-analysis-kit/mvp-recommendation-0.1"),
|
|
17
|
+
recommended: mvpCandidateSchema,
|
|
18
|
+
candidates: z.array(mvpCandidateSchema).min(1)
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
export const seriesMatchInputSchema = z.object({
|
|
22
|
+
matchId: z.string(),
|
|
23
|
+
model: matchWorkspaceModelSchema
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
export const seriesPlayerMapTrendSchema = z.object({
|
|
27
|
+
matchId: z.string(),
|
|
28
|
+
mapName: z.string(),
|
|
29
|
+
rivalhubRR: z.number().nonnegative(),
|
|
30
|
+
hltvRating: z.number().nonnegative(),
|
|
31
|
+
adr: z.number().nonnegative(),
|
|
32
|
+
kast: z.number().min(0).max(100)
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
export const seriesPlayerRowSchema = z.object({
|
|
36
|
+
playerKey: z.string(),
|
|
37
|
+
name: z.string(),
|
|
38
|
+
teamName: z.string(),
|
|
39
|
+
mapCount: z.number().int().positive(),
|
|
40
|
+
totalRounds: z.number().int().positive(),
|
|
41
|
+
kills: z.number().int().nonnegative(),
|
|
42
|
+
deaths: z.number().int().nonnegative(),
|
|
43
|
+
assists: z.number().int().nonnegative(),
|
|
44
|
+
adr: z.number().nonnegative(),
|
|
45
|
+
kast: z.number().min(0).max(100),
|
|
46
|
+
rivalhubRR: z.number().nonnegative(),
|
|
47
|
+
hltvRating: z.number().nonnegative(),
|
|
48
|
+
confidence: z.number().min(0).max(1),
|
|
49
|
+
perMap: z.array(seriesPlayerMapTrendSchema)
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
export const seriesSummarySchema = z.object({
|
|
53
|
+
version: z.literal("cs2-demo-analysis-kit/series-summary-0.1"),
|
|
54
|
+
mapCount: z.number().int().positive(),
|
|
55
|
+
maps: z.array(z.object({
|
|
56
|
+
matchId: z.string(),
|
|
57
|
+
mapName: z.string(),
|
|
58
|
+
scoreline: z.string(),
|
|
59
|
+
teamAName: z.string(),
|
|
60
|
+
teamBName: z.string(),
|
|
61
|
+
winnerName: z.string().nullable()
|
|
62
|
+
})),
|
|
63
|
+
teams: z.array(z.object({
|
|
64
|
+
teamKey: z.string(),
|
|
65
|
+
name: z.string(),
|
|
66
|
+
mapsWon: z.number().int().nonnegative()
|
|
67
|
+
})),
|
|
68
|
+
scoreboard: z.array(seriesPlayerRowSchema),
|
|
69
|
+
mvpCandidates: z.array(mvpCandidateSchema)
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
export type MvpCandidate = z.infer<typeof mvpCandidateSchema>;
|
|
73
|
+
export type MvpRecommendation = z.infer<typeof mvpRecommendationSchema>;
|
|
74
|
+
export type SeriesMatchInput = z.infer<typeof seriesMatchInputSchema>;
|
|
75
|
+
export type SeriesPlayerMapTrend = z.infer<typeof seriesPlayerMapTrendSchema>;
|
|
76
|
+
export type SeriesPlayerRow = z.infer<typeof seriesPlayerRowSchema>;
|
|
77
|
+
export type SeriesSummary = z.infer<typeof seriesSummarySchema>;
|
package/src/team.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { leaderboardMetricKeySchema } from "./leaderboard.js";
|
|
3
|
+
import { playerStyleAxisSchema } from "./player.js";
|
|
4
|
+
|
|
5
|
+
export const teamRosterInputSchema = z.object({
|
|
6
|
+
teamKey: z.string(),
|
|
7
|
+
name: z.string(),
|
|
8
|
+
playerKeys: z.array(z.string()).min(1)
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
export const teamMemberSummarySchema = z.object({
|
|
12
|
+
playerKey: z.string(),
|
|
13
|
+
name: z.string(),
|
|
14
|
+
mapCount: z.number().int().positive(),
|
|
15
|
+
confidence: z.number().min(0).max(1),
|
|
16
|
+
metrics: z.record(leaderboardMetricKeySchema, z.number().nullable())
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
export const teamMetricLeaderSchema = z.object({
|
|
20
|
+
metric: z.enum(["rivalhubRR", "adr", "kast", "firstKillPer100"]),
|
|
21
|
+
label: z.string(),
|
|
22
|
+
playerKey: z.string(),
|
|
23
|
+
name: z.string(),
|
|
24
|
+
value: z.number()
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
export const teamRoleSpecialistSchema = playerStyleAxisSchema.extend({
|
|
28
|
+
playerKey: z.string(),
|
|
29
|
+
name: z.string()
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
export const teamCohortSummarySchema = z.object({
|
|
33
|
+
version: z.literal("cs2-demo-analysis-kit/team-summary-0.1"),
|
|
34
|
+
weightsVersion: z.string(),
|
|
35
|
+
teamKey: z.string(),
|
|
36
|
+
name: z.string(),
|
|
37
|
+
members: z.array(teamMemberSummarySchema).min(1),
|
|
38
|
+
coreMembers: z.array(teamMemberSummarySchema).min(1),
|
|
39
|
+
averages: z.object({
|
|
40
|
+
rivalhubRR: z.number().nonnegative(),
|
|
41
|
+
hltvRating: z.number().nonnegative(),
|
|
42
|
+
adr: z.number().nonnegative(),
|
|
43
|
+
kd: z.number().nullable(),
|
|
44
|
+
kast: z.number().min(0).max(100),
|
|
45
|
+
confidence: z.number().min(0).max(1)
|
|
46
|
+
}),
|
|
47
|
+
performance: z.object({
|
|
48
|
+
firstKills: z.number().int().nonnegative(),
|
|
49
|
+
firstDeaths: z.number().int().nonnegative(),
|
|
50
|
+
openingDuelWinRate: z.number().min(0).max(1).nullable(),
|
|
51
|
+
clutchAttempts: z.number().int().nonnegative(),
|
|
52
|
+
clutchWins: z.number().int().nonnegative(),
|
|
53
|
+
clutchWinRate: z.number().min(0).max(1).nullable()
|
|
54
|
+
}),
|
|
55
|
+
/** 队伍 PRISM:成员各轴百分位的简单平均,不重算 PRISM。 */
|
|
56
|
+
style: z.object({
|
|
57
|
+
axes: z.array(playerStyleAxisSchema)
|
|
58
|
+
}).nullable(),
|
|
59
|
+
leaders: z.array(teamMetricLeaderSchema),
|
|
60
|
+
roleComplementarity: z.object({
|
|
61
|
+
/** 成员主轴覆盖率:不同主轴数 / min(成员数, 8),0–100。 */
|
|
62
|
+
coverageScore: z.number().min(0).max(100),
|
|
63
|
+
specialists: z.array(teamRoleSpecialistSchema),
|
|
64
|
+
weakAxes: z.array(playerStyleAxisSchema)
|
|
65
|
+
})
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
export type TeamRosterInput = z.infer<typeof teamRosterInputSchema>;
|
|
69
|
+
export type TeamMemberSummary = z.infer<typeof teamMemberSummarySchema>;
|
|
70
|
+
export type TeamMetricLeader = z.infer<typeof teamMetricLeaderSchema>;
|
|
71
|
+
export type TeamRoleSpecialist = z.infer<typeof teamRoleSpecialistSchema>;
|
|
72
|
+
export type TeamCohortSummary = z.infer<typeof teamCohortSummarySchema>;
|
package/src/trails.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { sideSchema, teamEconomySchema, grenadeTypeSchema } from "cs2-demo-format";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 开局动线:单选手在指定经济类型回合(默认长枪局)开局窗口内的
|
|
6
|
+
* 走位轨迹 + 道具投掷。由 @cs2dak/presentation 从 replay/rounds/grenades 派生,
|
|
7
|
+
* 坐标保持 world 坐标系,radar 投影由渲染端(@cs2dak/maps)完成。
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export const trailPointSchema = z.object({
|
|
11
|
+
/** 距 freeze 结束的秒数。 */
|
|
12
|
+
t: z.number().nonnegative(),
|
|
13
|
+
x: z.number(),
|
|
14
|
+
y: z.number()
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
export const trailGrenadeSchema = z.object({
|
|
18
|
+
/** 投掷时刻(距 freeze 结束的秒数)。 */
|
|
19
|
+
t: z.number().nonnegative(),
|
|
20
|
+
x: z.number(),
|
|
21
|
+
y: z.number(),
|
|
22
|
+
grenade: grenadeTypeSchema,
|
|
23
|
+
/** 生效时刻(距 freeze 结束的秒数)。 */
|
|
24
|
+
effectT: z.number().nonnegative(),
|
|
25
|
+
/** 效果消失时刻;导出包缺失时 null,渲染端按道具类型取默认时长。 */
|
|
26
|
+
destroyT: z.number().nonnegative().nullable(),
|
|
27
|
+
/** 落点(world 坐标)。 */
|
|
28
|
+
effectX: z.number(),
|
|
29
|
+
effectY: z.number()
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
export const openingTrailRoundSchema = z.object({
|
|
33
|
+
matchId: z.string(),
|
|
34
|
+
roundNumber: z.number().int().positive(),
|
|
35
|
+
side: sideSchema,
|
|
36
|
+
economyType: teamEconomySchema,
|
|
37
|
+
points: z.array(trailPointSchema),
|
|
38
|
+
grenades: z.array(trailGrenadeSchema)
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
export const openingTrailsModelSchema = z.object({
|
|
42
|
+
version: z.literal("cs2-demo-analysis-kit/opening-trails-0.2"),
|
|
43
|
+
matchId: z.string(),
|
|
44
|
+
mapName: z.string(),
|
|
45
|
+
steamId64: z.string(),
|
|
46
|
+
playerName: z.string(),
|
|
47
|
+
/** 该导出包是否含回放流;false 时 rounds 为空。 */
|
|
48
|
+
available: z.boolean(),
|
|
49
|
+
windowSeconds: z.number().positive(),
|
|
50
|
+
rounds: z.array(openingTrailRoundSchema)
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
export type TrailPoint = z.infer<typeof trailPointSchema>;
|
|
54
|
+
export type TrailGrenade = z.infer<typeof trailGrenadeSchema>;
|
|
55
|
+
export type OpeningTrailRound = z.infer<typeof openingTrailRoundSchema>;
|
|
56
|
+
export type OpeningTrailsModel = z.infer<typeof openingTrailsModelSchema>;
|
package/src/upstream.ts
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import {
|
|
3
|
+
sideSchema,
|
|
4
|
+
teamKeySchema,
|
|
5
|
+
economyTypeSchema,
|
|
6
|
+
teamEconomySchema,
|
|
7
|
+
vec3Schema,
|
|
8
|
+
playerIndexSchema,
|
|
9
|
+
playerRowSchema,
|
|
10
|
+
roundRowSchema,
|
|
11
|
+
killRowSchema,
|
|
12
|
+
damageRowSchema,
|
|
13
|
+
playerEconomyRowSchema,
|
|
14
|
+
playerStatsRowSchema,
|
|
15
|
+
blindRowSchema,
|
|
16
|
+
bombRowSchema,
|
|
17
|
+
grenadeRowSchema,
|
|
18
|
+
clutchRowSchema,
|
|
19
|
+
shotTrackSchema,
|
|
20
|
+
shotsSchema,
|
|
21
|
+
replaySchema,
|
|
22
|
+
replayRoundSchema,
|
|
23
|
+
replayPlayerTrackSchema,
|
|
24
|
+
replayProjectileSchema,
|
|
25
|
+
duelsSchema,
|
|
26
|
+
duelWindowSchema,
|
|
27
|
+
duelPlayerTrackSchema,
|
|
28
|
+
duelAnchorSchema,
|
|
29
|
+
} from "cs2-demo-format";
|
|
30
|
+
|
|
31
|
+
export {
|
|
32
|
+
SCHEMAS_BY_KEY,
|
|
33
|
+
blindRowSchema,
|
|
34
|
+
blindsSchema,
|
|
35
|
+
bombEventTypeSchema,
|
|
36
|
+
bombRowSchema,
|
|
37
|
+
bombsSchema,
|
|
38
|
+
clutchRowSchema,
|
|
39
|
+
clutchesSchema,
|
|
40
|
+
damageRowSchema,
|
|
41
|
+
damagesSchema,
|
|
42
|
+
decodeDelta,
|
|
43
|
+
duelAnchorSchema,
|
|
44
|
+
duelPlayerTrackSchema,
|
|
45
|
+
duelsSchema,
|
|
46
|
+
duelWindowSchema,
|
|
47
|
+
economyTypeSchema,
|
|
48
|
+
endReasonSchema,
|
|
49
|
+
FLAG_ALIVE,
|
|
50
|
+
FLAG_HAS_BOMB,
|
|
51
|
+
FLAG_HAS_DEFUSE_KIT,
|
|
52
|
+
grenadeRowSchema,
|
|
53
|
+
grenadeTypeSchema,
|
|
54
|
+
grenadesSchema,
|
|
55
|
+
hitgroupSchema,
|
|
56
|
+
killRowSchema,
|
|
57
|
+
killsSchema,
|
|
58
|
+
manifestSchema,
|
|
59
|
+
matchSchema,
|
|
60
|
+
playerEconomiesSchema,
|
|
61
|
+
playerEconomyRowSchema,
|
|
62
|
+
playerIndexSchema,
|
|
63
|
+
playerRowSchema,
|
|
64
|
+
playersSchema,
|
|
65
|
+
playerStatsRowSchema,
|
|
66
|
+
playerStatsSchema,
|
|
67
|
+
replaySchema,
|
|
68
|
+
replayPlayerTrackSchema,
|
|
69
|
+
replayProjectileSchema,
|
|
70
|
+
replayRoundSchema,
|
|
71
|
+
roundRowSchema,
|
|
72
|
+
roundsSchema,
|
|
73
|
+
shotTrackSchema,
|
|
74
|
+
shotsSchema,
|
|
75
|
+
sideSchema,
|
|
76
|
+
steamId64Schema,
|
|
77
|
+
teamEconomySchema,
|
|
78
|
+
teamEconomyTypeSchema,
|
|
79
|
+
teamKeySchema,
|
|
80
|
+
teamSummarySchema,
|
|
81
|
+
vec3Schema,
|
|
82
|
+
} from "cs2-demo-format";
|
|
83
|
+
|
|
84
|
+
export type Side = z.infer<typeof sideSchema>;
|
|
85
|
+
export type TeamKey = z.infer<typeof teamKeySchema>;
|
|
86
|
+
export type EconomyType = z.infer<typeof economyTypeSchema>;
|
|
87
|
+
export type TeamEconomyType = z.infer<typeof teamEconomySchema>;
|
|
88
|
+
export type Vec3 = z.infer<typeof vec3Schema>;
|
|
89
|
+
export type PlayerIndex = z.infer<typeof playerIndexSchema>;
|
|
90
|
+
|
|
91
|
+
export type PackagePlayer = z.infer<typeof playerRowSchema>;
|
|
92
|
+
export type PackageRound = z.infer<typeof roundRowSchema>;
|
|
93
|
+
export type PackageKill = z.infer<typeof killRowSchema>;
|
|
94
|
+
export type PackageDamage = z.infer<typeof damageRowSchema>;
|
|
95
|
+
export type PackagePlayerEconomy = z.infer<typeof playerEconomyRowSchema>;
|
|
96
|
+
export type PackagePlayerStats = z.infer<typeof playerStatsRowSchema>;
|
|
97
|
+
export type PackageBlind = z.infer<typeof blindRowSchema>;
|
|
98
|
+
export type PackageBomb = z.infer<typeof bombRowSchema>;
|
|
99
|
+
export type PackageGrenade = z.infer<typeof grenadeRowSchema>;
|
|
100
|
+
export type PackageClutch = z.infer<typeof clutchRowSchema>;
|
|
101
|
+
export type PackageShots = z.infer<typeof shotsSchema>;
|
|
102
|
+
export type PackageShotTrack = z.infer<typeof shotTrackSchema>;
|
|
103
|
+
export type Replay = z.infer<typeof replaySchema>;
|
|
104
|
+
export type ReplayRound = z.infer<typeof replayRoundSchema>;
|
|
105
|
+
export type ReplayPlayerTrack = z.infer<typeof replayPlayerTrackSchema>;
|
|
106
|
+
export type ReplayProjectile = z.infer<typeof replayProjectileSchema>;
|
|
107
|
+
export type Duels = z.infer<typeof duelsSchema>;
|
|
108
|
+
export type DuelWindow = z.infer<typeof duelWindowSchema>;
|
|
109
|
+
export type DuelPlayerTrack = z.infer<typeof duelPlayerTrackSchema>;
|
|
110
|
+
export type DuelAnchor = z.infer<typeof duelAnchorSchema>;
|
package/src/veto.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
export const seriesFormatSchema = z.enum(["bo1", "bo3", "bo5"]);
|
|
4
|
+
export const vetoActionTypeSchema = z.enum(["ban", "pick", "decider"]);
|
|
5
|
+
export const vetoTeamRefSchema = z.enum(["teamA", "teamB"]).nullable();
|
|
6
|
+
|
|
7
|
+
export const seriesVetoStepSchema = z.object({
|
|
8
|
+
stepOrder: z.number().int().positive(),
|
|
9
|
+
actionType: vetoActionTypeSchema,
|
|
10
|
+
mapName: z.string().min(1),
|
|
11
|
+
teamKey: vetoTeamRefSchema,
|
|
12
|
+
side: z.enum(["t", "ct"]).nullable()
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
export const seriesVetoSchema = z.object({
|
|
16
|
+
version: z.literal("cs2-demo-analysis-kit/series-veto-0.1"),
|
|
17
|
+
seriesId: z.string(),
|
|
18
|
+
format: seriesFormatSchema,
|
|
19
|
+
teamAName: z.string(),
|
|
20
|
+
teamBName: z.string(),
|
|
21
|
+
mapPool: z.array(z.string().min(1)),
|
|
22
|
+
maps: z.object({
|
|
23
|
+
picked: z.array(z.object({
|
|
24
|
+
mapName: z.string().min(1),
|
|
25
|
+
teamKey: z.enum(["teamA", "teamB"]).nullable()
|
|
26
|
+
})),
|
|
27
|
+
banned: z.array(z.object({
|
|
28
|
+
mapName: z.string().min(1),
|
|
29
|
+
teamKey: z.enum(["teamA", "teamB"]).nullable()
|
|
30
|
+
})),
|
|
31
|
+
decider: z.string().min(1).nullable()
|
|
32
|
+
}),
|
|
33
|
+
sideChoices: z.array(z.object({
|
|
34
|
+
mapName: z.string().min(1),
|
|
35
|
+
teamKey: z.enum(["teamA", "teamB"]).nullable(),
|
|
36
|
+
side: z.enum(["t", "ct"])
|
|
37
|
+
})),
|
|
38
|
+
steps: z.array(seriesVetoStepSchema)
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
export type SeriesFormat = z.infer<typeof seriesFormatSchema>;
|
|
42
|
+
export type VetoActionType = z.infer<typeof vetoActionTypeSchema>;
|
|
43
|
+
export type SeriesVetoStep = z.infer<typeof seriesVetoStepSchema>;
|
|
44
|
+
export type SeriesVeto = z.infer<typeof seriesVetoSchema>;
|