@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 CHANGED
@@ -1,3 +1,10 @@
1
+ Licensing note / 适用范围
2
+ -------------------------
3
+ The MIT License below applies to this repository EXCEPT apps/dak-studio/,
4
+ which is licensed under the GNU AGPL-3.0-only (see apps/dak-studio/LICENSE).
5
+ Ecosystem packages (@cs2dak/*, python exporter) are MIT; the DAK Studio
6
+ product is AGPL. Third-party attributions: THIRD-PARTY-NOTICES.md.
7
+
1
8
  MIT License
2
9
 
3
10
  Copyright (c) 2026 Starfie1d
package/package.json CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "name": "@cs2dak/contract",
3
- "version": "0.2.1",
3
+ "version": "1.1.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "exports": {
7
7
  ".": "./src/index.ts"
8
8
  },
9
9
  "dependencies": {
10
- "@rivalhub/rival-rating": "^0.1.0",
11
- "cs2-demo-format": "^2.2.0",
10
+ "@rivalhub/rival-rating": "^0.3.0",
11
+ "cs2-demo-format": "^3.1.0",
12
12
  "zod": "^3.25.0"
13
13
  },
14
14
  "files": [
@@ -0,0 +1,228 @@
1
+ import type { RRResult, PrismResult } from "@rivalhub/rival-rating";
2
+ import { z } from "zod";
3
+ import { teamKeySchema, sideSchema, economyTypeSchema, teamEconomySchema, grenadeTypeSchema } from "cs2-demo-format";
4
+ import { qaReportSchema } from "./qa.js";
5
+ import { rrIndicatorsSchema } from "./scoring.js";
6
+
7
+ export const playerRoundFactSchema = z.object({
8
+ roundNumber: z.number().int().positive(),
9
+ steamId64: z.string(),
10
+ name: z.string(),
11
+ teamKey: teamKeySchema,
12
+ side: sideSchema,
13
+ survived: z.boolean(),
14
+ kills: z.number().int().nonnegative(),
15
+ deaths: z.number().int().nonnegative(),
16
+ assists: z.number().int().nonnegative(),
17
+ damage: z.number().int().nonnegative(),
18
+ utilityDamage: z.number().int().nonnegative(),
19
+ flashAssists: z.number().int().nonnegative(),
20
+ tradeKills: z.number().int().nonnegative(),
21
+ tradedDeaths: z.number().int().nonnegative(),
22
+ openingDuel: z.enum(["none", "won", "lost"]),
23
+ kastTags: z.array(z.enum(["kill", "assist", "survive", "trade"])),
24
+ equipmentValue: z.number().int().nonnegative().nullable(),
25
+ economyType: economyTypeSchema.nullable()
26
+ });
27
+
28
+ export const playerIndicatorRowSchema = z.object({
29
+ steamId64: z.string(),
30
+ name: z.string(),
31
+ teamKey: teamKeySchema,
32
+ indicators: rrIndicatorsSchema,
33
+ rr: z.custom<RRResult>(),
34
+ rrPercentile: z.number().min(0).max(100),
35
+ prism: z.custom<PrismResult>().nullable()
36
+ });
37
+
38
+ export const playerScoreboardRowSchema = z.object({
39
+ steamId64: z.string(),
40
+ name: z.string(),
41
+ teamKey: teamKeySchema,
42
+ indicators: rrIndicatorsSchema,
43
+ kills: z.number().int().nonnegative(),
44
+ deaths: z.number().int().nonnegative(),
45
+ assists: z.number().int().nonnegative(),
46
+ adr: z.number().nonnegative(),
47
+ kast: z.number().min(0).max(100),
48
+ headshotPercent: z.number().min(0).max(100),
49
+ entryKills: z.number().int().nonnegative(),
50
+ tradeKills: z.number().int().nonnegative(),
51
+ awpKills: z.number().int().nonnegative(),
52
+ utilityDamage: z.number().int().nonnegative(),
53
+ combatDeathCount: z.number().int().nonnegative().nullable(),
54
+ bombDeathCount: z.number().int().nonnegative().nullable(),
55
+ wallbangKillCount: z.number().int().nonnegative().nullable(),
56
+ noScopeKillCount: z.number().int().nonnegative().nullable(),
57
+ throughSmokeKillCount: z.number().int().nonnegative().nullable(),
58
+ collateralKillCount: z.number().int().nonnegative().nullable(),
59
+ bombPlantCount: z.number().int().nonnegative().nullable(),
60
+ bombDefuseCount: z.number().int().nonnegative().nullable(),
61
+ confidence: z.number().min(0).max(1),
62
+ fieldAvailability: z.object({
63
+ playerStats: z.enum(["available", "missing"]),
64
+ economy: z.enum(["available", "missing"]),
65
+ rounds: z.enum(["available", "missing"]),
66
+ richKills: z.enum(["available", "partial", "missing"]),
67
+ damages: z.enum(["available", "missing"]),
68
+ bombs: z.enum(["available", "missing"])
69
+ }),
70
+ // rrBase(clamp 前的基础分):被碾压方 KAST/KPR 低、死亡惩罚大时可为负,
71
+ // 不能强制非负,否则一边倒的真实比赛整场导入失败。最终 rr 才被 clamp 到非负。
72
+ ratingSeed: z.number(),
73
+ rr: z.number().nonnegative(),
74
+ rrPercentile: z.number().min(0).max(100),
75
+ /** RR v2 账户分。单场为 frozen pro baseline:1.00 = 职业基线。 */
76
+ accountRR: z.number().nonnegative(),
77
+ /** 锚定/clamp 前的原始账户分(调试用)。 */
78
+ accountRRRaw: z.number(),
79
+ /** Combat 击杀项的 context 乘子(1.0 = context 未生效 / 已降级)。 */
80
+ accountCombatContextFactor: z.number(),
81
+ /** 六账户对 RR 的加权贡献(解释面板用)。 */
82
+ accountBreakdown: z.object({
83
+ combat: z.number(),
84
+ trade: z.number(),
85
+ mapControl: z.number(),
86
+ clutch: z.number(),
87
+ objective: z.number(),
88
+ utility: z.number()
89
+ }),
90
+ /**
91
+ * Combat context 分桶的可用性。
92
+ * "available" = 采集到数据源(即使无相关样本,分桶为 0 也算 available);
93
+ * "missing" = 数据源缺失(parser 未产出),乘子已降级为 1.0,前端应显示"未启用"。
94
+ */
95
+ accountContextStatus: z.object({
96
+ buyDelta: z.enum(["available", "missing"]),
97
+ manState: z.enum(["available", "missing"])
98
+ })
99
+ });
100
+
101
+ export const weaponKillRowSchema = z.object({
102
+ weapon: z.string(),
103
+ kills: z.number().int().nonnegative(),
104
+ headshotKills: z.number().int().nonnegative(),
105
+ tradeKills: z.number().int().nonnegative(),
106
+ noScopeKills: z.number().int().nonnegative(),
107
+ throughSmokeKills: z.number().int().nonnegative(),
108
+ wallbangKills: z.number().int().nonnegative(),
109
+ penetratedObjects: z.number().int().nonnegative()
110
+ });
111
+
112
+ export const playerWeaponHighlightFactsSchema = z.object({
113
+ steamId64: z.string(),
114
+ totalKills: z.number().int().nonnegative(),
115
+ weapons: z.array(weaponKillRowSchema),
116
+ highlights: z.object({
117
+ wallbangKills: z.number().int().nonnegative().nullable(),
118
+ noScopeKills: z.number().int().nonnegative().nullable(),
119
+ throughSmokeKills: z.number().int().nonnegative().nullable(),
120
+ collateralKills: z.number().int().nonnegative().nullable()
121
+ })
122
+ });
123
+
124
+ export const accountContextAvailabilitySchema = z.enum(["available", "partial", "missing"]);
125
+
126
+ export const timelineEventSchema = z.object({
127
+ id: z.string(),
128
+ roundNumber: z.number().int().positive(),
129
+ tick: z.number().int().positive(),
130
+ timeSeconds: z.number().nonnegative(),
131
+ clockPhase: z.enum(["freeze", "round", "bomb", "round-end"]),
132
+ clockSeconds: z.number().nonnegative(),
133
+ clockLabel: z.string(),
134
+ type: z.enum(["kill", "bomb", "grenade", "round-end"]),
135
+ label: z.string(),
136
+ teamKey: teamKeySchema.nullable()
137
+ });
138
+
139
+ export const economyPointSchema = z.object({
140
+ roundNumber: z.number().int().positive(),
141
+ teamA: z.number().int().nonnegative(),
142
+ teamB: z.number().int().nonnegative(),
143
+ advantage: z.number().int(),
144
+ teamAEconomy: teamEconomySchema,
145
+ teamBEconomy: teamEconomySchema,
146
+ winnerTeamKey: teamKeySchema
147
+ });
148
+
149
+ export const heatmapPointSchema = z.object({
150
+ x: z.number(),
151
+ y: z.number(),
152
+ z: z.number(),
153
+ roundNumber: z.number().int().positive(),
154
+ teamKey: teamKeySchema.nullable(),
155
+ steamId64: z.string().nullable(),
156
+ side: sideSchema.nullable(),
157
+ kind: z.enum(["kill", "death", "grenade"]),
158
+ grenadeType: grenadeTypeSchema.nullable()
159
+ });
160
+
161
+ export const mapViewSchema = z.object({
162
+ name: z.string(),
163
+ radarImageUrl: z.string().nullable(),
164
+ /** 双层地图(de_nuke / de_vertigo)的下层底图;单层地图为 null。 */
165
+ lowerRadarImageUrl: z.string().nullable().optional().default(null),
166
+ calibrated: z.boolean()
167
+ });
168
+
169
+ export const analysisProvenanceSchema = z.object({
170
+ analysisVersion: z.string(),
171
+ sourceSchemaVersion: z.literal("cs2-demo-format/3.0"),
172
+ sourceDemoHash: z.string().nullable(),
173
+ exporter: z.object({ name: z.string(), version: z.string() }),
174
+ parser: z.object({ name: z.string(), version: z.string() }),
175
+ ratingVersions: z.object({
176
+ rr: z.string().nullable(),
177
+ valueAccounts: z.string().nullable()
178
+ })
179
+ });
180
+
181
+ export const analysisBundleSchema = z.object({
182
+ version: z.literal("cs2-demo-analysis-kit/1.0"),
183
+ sourceSchemaVersion: z.literal("cs2-demo-format/3.0"),
184
+ provenance: analysisProvenanceSchema,
185
+ mapName: z.string(),
186
+ tickrate: z.number().int().positive(),
187
+ teams: z.object({
188
+ teamA: z.object({ name: z.string(), score: z.number().int().nonnegative() }),
189
+ teamB: z.object({ name: z.string(), score: z.number().int().nonnegative() })
190
+ }),
191
+ scoreboard: z.array(playerScoreboardRowSchema),
192
+ playerWeaponHighlights: z.array(playerWeaponHighlightFactsSchema),
193
+ playerIndicators: z.array(playerIndicatorRowSchema),
194
+ playerRoundFacts: z.array(playerRoundFactSchema),
195
+ timeline: z.array(timelineEventSchema),
196
+ economy: z.array(economyPointSchema),
197
+ heatmap: z.array(heatmapPointSchema),
198
+ qa: qaReportSchema
199
+ });
200
+
201
+ export const demoViewModelSchema = z.object({
202
+ title: z.string(),
203
+ subtitle: z.string(),
204
+ map: mapViewSchema,
205
+ scoreline: z.string(),
206
+ teams: analysisBundleSchema.shape.teams,
207
+ scoreboard: z.array(playerScoreboardRowSchema),
208
+ playerIndicators: z.array(playerIndicatorRowSchema),
209
+ playerRoundFacts: z.array(playerRoundFactSchema),
210
+ timeline: z.array(timelineEventSchema),
211
+ economy: z.array(economyPointSchema),
212
+ heatmap: z.array(heatmapPointSchema),
213
+ qa: qaReportSchema
214
+ });
215
+
216
+ export type PlayerRoundFact = z.infer<typeof playerRoundFactSchema>;
217
+ export type PlayerIndicatorRow = z.infer<typeof playerIndicatorRowSchema>;
218
+ export type PlayerScoreboardRow = z.infer<typeof playerScoreboardRowSchema>;
219
+ export type WeaponKillRow = z.infer<typeof weaponKillRowSchema>;
220
+ export type PlayerWeaponHighlightFacts = z.infer<typeof playerWeaponHighlightFactsSchema>;
221
+ export type AccountContextAvailability = z.infer<typeof accountContextAvailabilitySchema>;
222
+ export type TimelineEvent = z.infer<typeof timelineEventSchema>;
223
+ export type EconomyPoint = z.infer<typeof economyPointSchema>;
224
+ export type GrenadeType = z.infer<typeof grenadeTypeSchema>;
225
+ export type HeatmapPoint = z.infer<typeof heatmapPointSchema>;
226
+ export type AnalysisBundle = z.infer<typeof analysisBundleSchema>;
227
+ export type AnalysisProvenance = z.infer<typeof analysisProvenanceSchema>;
228
+ export type DemoViewModel = z.infer<typeof demoViewModelSchema>;
package/src/bracket.ts ADDED
@@ -0,0 +1,68 @@
1
+ /**
2
+ * 赛事 bracket 的中性数据模型 —— 观看侧(从比赛结果推算)与制作器(从框架槽位)共用同一套
3
+ * 渲染。瑞士轮按战绩组(Buchholz)成列 + 晋级/淘汰终列;淘汰赛按晋级轮次成列。
4
+ */
5
+
6
+ export interface BracketCell {
7
+ key: string;
8
+ teamA: string | null;
9
+ teamB: string | null;
10
+ scoreA: number | null;
11
+ scoreB: number | null;
12
+ winner: "A" | "B" | null;
13
+ date: string | null;
14
+ /** 观看侧:点开该场的 demo entry。 */
15
+ entryIds?: string[];
16
+ /** 制作器:待填槽位(点击附加 demo)。 */
17
+ empty?: boolean;
18
+ }
19
+
20
+ export interface SwissGroup {
21
+ /** 战绩组标签,如 "1-0"。 */
22
+ record: string;
23
+ matches: BracketCell[];
24
+ /** 制作器:该战绩组可继续添加比赛(携带 slot id)。 */
25
+ addSlotId?: string;
26
+ }
27
+
28
+ export interface SwissColumn {
29
+ round: number;
30
+ groups: SwissGroup[];
31
+ }
32
+
33
+ export interface SwissModel {
34
+ columns: SwissColumn[];
35
+ advanced: { team: string; record: string }[];
36
+ eliminated: { team: string; record: string }[];
37
+ winsTarget: number;
38
+ lossTarget: number;
39
+ }
40
+
41
+ export interface ElimColumn {
42
+ round: number;
43
+ label: string;
44
+ matches: BracketCell[];
45
+ }
46
+
47
+ /** 淘汰赛 bracket 中单个节点的元数据(双败 / GSL lane-aware 布局用)。 */
48
+ export interface ElimNode {
49
+ id: string;
50
+ round: number;
51
+ lane: "winner" | "loser" | "grand" | "single";
52
+ label: string;
53
+ nextWinNodeId?: string | null;
54
+ nextLossNodeId?: string | null;
55
+ }
56
+
57
+ export interface ElimModel {
58
+ columns: ElimColumn[];
59
+ /**
60
+ * Lane-aware 节点元数据(可选)。**存在性决定渲染模式**:
61
+ * - 非空 → SVG lane-aware 布局(双败 / GSL),含晋级连线与 lane 分隔
62
+ * - undefined / [] → DOM 列布局(单败 / 旧资产 / 制作器),以 MatchBox 列展示
63
+ *
64
+ * 未来考虑改为显式 `renderMode?: "dom" | "svg-lane"` 以消除隐式联合类型。
65
+ */
66
+ nodes?: ElimNode[];
67
+ }
68
+
package/src/cohort.ts ADDED
@@ -0,0 +1,60 @@
1
+ import type { PrismResult } from "@rivalhub/rival-rating";
2
+ import { z } from "zod";
3
+ import { teamKeySchema } from "cs2-demo-format";
4
+ import { accountContextAvailabilitySchema } from "./analysis.js";
5
+ import { rrIndicatorsSchema } from "./scoring.js";
6
+ import { playerWeaponHighlightFactsSchema } from "./analysis.js";
7
+
8
+ export const seasonPlayerRowSchema = z.object({
9
+ playerKey: z.string(),
10
+ steamIds: z.array(z.string()),
11
+ primarySteamId64: z.string(),
12
+ externalUserId: z.string().nullable(),
13
+ name: z.string(),
14
+ teamKeys: z.array(teamKeySchema),
15
+ mapCount: z.number().int().positive(),
16
+ rrV1: z.number().nonnegative(),
17
+ rrV1Percentile: z.number().min(0).max(100),
18
+ indicators: rrIndicatorsSchema,
19
+ weaponHighlights: playerWeaponHighlightFactsSchema,
20
+ accountRR: z.number().nonnegative(),
21
+ accountRRRaw: z.number(),
22
+ accountBreakdown: z.object({
23
+ combat: z.number(),
24
+ trade: z.number(),
25
+ mapControl: z.number(),
26
+ clutch: z.number(),
27
+ objective: z.number(),
28
+ utility: z.number()
29
+ }),
30
+ accountContextStatus: z.object({
31
+ buyDelta: accountContextAvailabilitySchema,
32
+ manState: accountContextAvailabilitySchema
33
+ }),
34
+ prism: z.custom<PrismResult>().nullable(),
35
+ confidence: z.number().min(0).max(1),
36
+ perMatch: z.array(z.object({
37
+ matchId: z.string(),
38
+ steamId64: z.string(),
39
+ accountRR: z.number().nonnegative(),
40
+ rrV1: z.number().nonnegative()
41
+ }))
42
+ });
43
+
44
+ export const seasonCohortBundleSchema = z.object({
45
+ version: z.literal("cs2-demo-analysis-kit/cohort-1.0"),
46
+ matchCount: z.number().int().nonnegative(),
47
+ players: z.array(seasonPlayerRowSchema),
48
+ weightsVersion: z.string(),
49
+ provenance: z.object({
50
+ cohortVersion: z.literal("cs2-demo-analysis-kit/cohort-1.0"),
51
+ sourceSchemaVersion: z.literal("cs2-demo-format/3.0"),
52
+ matches: z.array(z.object({
53
+ matchId: z.string(),
54
+ sourceDemoHash: z.string().nullable()
55
+ }))
56
+ })
57
+ });
58
+
59
+ export type SeasonPlayerRow = z.infer<typeof seasonPlayerRowSchema>;
60
+ export type SeasonCohortBundle = z.infer<typeof seasonCohortBundleSchema>;