@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/workspace.ts
ADDED
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export { seriesVetoSchema, type SeriesVeto } from "./veto.js";
|
|
3
|
+
import { teamKeySchema, sideSchema, grenadeTypeSchema, teamEconomySchema } from "cs2-demo-format";
|
|
4
|
+
import { qaReportSchema } from "./qa.js";
|
|
5
|
+
import {
|
|
6
|
+
analysisBundleSchema,
|
|
7
|
+
timelineEventSchema,
|
|
8
|
+
economyPointSchema,
|
|
9
|
+
playerScoreboardRowSchema,
|
|
10
|
+
playerRoundFactSchema,
|
|
11
|
+
mapViewSchema,
|
|
12
|
+
} from "./analysis.js";
|
|
13
|
+
|
|
14
|
+
export const workspaceTabSchema = z.object({
|
|
15
|
+
key: z.enum(["overview", "rounds", "players", "economy", "weapons", "duels", "map", "replay"]),
|
|
16
|
+
label: z.string()
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
export const workspaceKpiSchema = z.object({
|
|
20
|
+
key: z.string(),
|
|
21
|
+
label: z.string(),
|
|
22
|
+
value: z.string(),
|
|
23
|
+
detail: z.string()
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
export const workspaceOverviewSchema = z.object({
|
|
27
|
+
kpis: z.array(workspaceKpiSchema),
|
|
28
|
+
story: z.array(z.string())
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
/** 回合筛选与时间轴用的派生事实(v0.2 query-first),由 presentation 从
|
|
32
|
+
* kills/bombs/clutches 一次性算好,React 端只做谓词匹配。 */
|
|
33
|
+
export const workspaceRoundFacetsSchema = z.object({
|
|
34
|
+
/** 下包点;该回合未下包为 null。 */
|
|
35
|
+
bombSite: z.enum(["a", "b"]).nullable(),
|
|
36
|
+
bombPlantTick: z.number().int().positive().nullable(),
|
|
37
|
+
bombDefuseTick: z.number().int().positive().nullable(),
|
|
38
|
+
firstKillTick: z.number().int().positive().nullable(),
|
|
39
|
+
firstKillSteamId64: z.string().nullable(),
|
|
40
|
+
firstKillTeamKey: teamKeySchema.nullable(),
|
|
41
|
+
/** 该回合的残局(若有):clutch start tick 作时间轴锚点。 */
|
|
42
|
+
clutch: z.object({
|
|
43
|
+
steamId64: z.string(),
|
|
44
|
+
teamKey: teamKeySchema,
|
|
45
|
+
opponentCount: z.number().int().min(1).max(5),
|
|
46
|
+
won: z.boolean(),
|
|
47
|
+
tick: z.number().int().positive()
|
|
48
|
+
}).nullable(),
|
|
49
|
+
/** 单人单回合最高击杀数(≥3 即多杀回合)。 */
|
|
50
|
+
maxKillsByOnePlayer: z.number().int().nonnegative(),
|
|
51
|
+
wallbangKills: z.number().int().nonnegative(),
|
|
52
|
+
throughSmokeKills: z.number().int().nonnegative()
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
export const workspaceRoundSchema = z.object({
|
|
56
|
+
roundNumber: z.number().int().positive(),
|
|
57
|
+
scoreBefore: z.string(),
|
|
58
|
+
winnerTeamKey: teamKeySchema,
|
|
59
|
+
winnerSide: sideSchema,
|
|
60
|
+
endReason: z.string(),
|
|
61
|
+
teamAEconomy: teamEconomySchema,
|
|
62
|
+
teamBEconomy: teamEconomySchema,
|
|
63
|
+
events: z.array(timelineEventSchema),
|
|
64
|
+
playerFacts: z.array(playerRoundFactSchema),
|
|
65
|
+
/** 旧模型可缺省;缺省时回合筛选器自动隐藏对应维度。 */
|
|
66
|
+
facets: workspaceRoundFacetsSchema.optional()
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
export const workspacePlayerSchema = z.object({
|
|
70
|
+
row: playerScoreboardRowSchema,
|
|
71
|
+
teamName: z.string(),
|
|
72
|
+
summary: z.array(z.string()),
|
|
73
|
+
rrBreakdown: z.array(z.object({
|
|
74
|
+
key: z.enum(["combat", "trade", "mapControl", "clutch", "objective", "utility"]),
|
|
75
|
+
label: z.string(),
|
|
76
|
+
value: z.number()
|
|
77
|
+
})),
|
|
78
|
+
roundFacts: z.array(playerRoundFactSchema)
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
/** 武器统计行(比赛级,按击杀数降序)。 */
|
|
82
|
+
export const workspaceWeaponRowSchema = z.object({
|
|
83
|
+
weapon: z.string(),
|
|
84
|
+
label: z.string(),
|
|
85
|
+
kills: z.number().int().nonnegative(),
|
|
86
|
+
headshotPercent: z.number().min(0).max(100).nullable(),
|
|
87
|
+
/** 有效生命伤害合计(healthDamage 口径,与 ADR 一致)。 */
|
|
88
|
+
damage: z.number().int().nonnegative(),
|
|
89
|
+
wallbangKills: z.number().int().nonnegative(),
|
|
90
|
+
noScopeKills: z.number().int().nonnegative(),
|
|
91
|
+
throughSmokeKills: z.number().int().nonnegative(),
|
|
92
|
+
topKillerName: z.string().nullable(),
|
|
93
|
+
topKillerKills: z.number().int().nonnegative()
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
export const workspaceDuelPlayerSchema = z.object({
|
|
97
|
+
steamId64: z.string(),
|
|
98
|
+
name: z.string(),
|
|
99
|
+
teamKey: teamKeySchema
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
/** 对位:击杀矩阵 + 开局对枪统计。players 顺序 = teamA 在前。 */
|
|
103
|
+
export const workspaceDuelsSchema = z.object({
|
|
104
|
+
players: z.array(workspaceDuelPlayerSchema),
|
|
105
|
+
/** matrix[i][j] = players[i] 击杀 players[j] 的次数(含队友误伤致死)。 */
|
|
106
|
+
matrix: z.array(z.array(z.number().int().nonnegative())),
|
|
107
|
+
openings: z.array(z.object({
|
|
108
|
+
steamId64: z.string(),
|
|
109
|
+
name: z.string(),
|
|
110
|
+
teamKey: teamKeySchema,
|
|
111
|
+
openingKills: z.number().int().nonnegative(),
|
|
112
|
+
openingDeaths: z.number().int().nonnegative(),
|
|
113
|
+
/** 开局对枪胜率(0–100);无开局对枪时 null。 */
|
|
114
|
+
winRatePercent: z.number().min(0).max(100).nullable()
|
|
115
|
+
}))
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
export const workspaceMapModeSchema = z.object({
|
|
119
|
+
key: z.enum(["death", "kill", "grenade", "bomb", "position"]),
|
|
120
|
+
label: z.string(),
|
|
121
|
+
count: z.number().int().nonnegative()
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
export const workspaceSpatialPointSchema = z.object({
|
|
125
|
+
x: z.number(),
|
|
126
|
+
y: z.number(),
|
|
127
|
+
z: z.number(),
|
|
128
|
+
roundNumber: z.number().int().positive(),
|
|
129
|
+
teamKey: teamKeySchema.nullable(),
|
|
130
|
+
steamId64: z.string().nullable(),
|
|
131
|
+
side: sideSchema.nullable(),
|
|
132
|
+
kind: z.enum(["kill", "death", "grenade", "bomb", "position"]),
|
|
133
|
+
grenadeType: grenadeTypeSchema.nullable()
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
export const workspaceMapSchema = z.object({
|
|
137
|
+
view: mapViewSchema,
|
|
138
|
+
modes: z.array(workspaceMapModeSchema),
|
|
139
|
+
points: z.array(workspaceSpatialPointSchema),
|
|
140
|
+
status: z.object({
|
|
141
|
+
hasRadar: z.boolean(),
|
|
142
|
+
hasPositionData: z.boolean(),
|
|
143
|
+
message: z.string().nullable()
|
|
144
|
+
})
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
export const workspaceReplayLoadoutSchema = z.object({
|
|
148
|
+
/** 回合 freeze time 的主武器;手枪/eco 局可能为 null。 */
|
|
149
|
+
primaryWeapon: z.string().nullable(),
|
|
150
|
+
/** 回合 freeze time 的副武器;极少数丢枪/拾枪状态可能为 null。 */
|
|
151
|
+
secondaryWeapon: z.string().nullable(),
|
|
152
|
+
grenadeCount: z.number().int().nonnegative(),
|
|
153
|
+
/** 具体持有道具类型;旧 v3 导出包只有 grenadeCount,此数组为空。 */
|
|
154
|
+
grenades: z.array(grenadeTypeSchema).default([])
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
export const workspaceReplayFrameSchema = z.object({
|
|
158
|
+
tick: z.number().int().positive(),
|
|
159
|
+
x: z.number(),
|
|
160
|
+
y: z.number(),
|
|
161
|
+
z: z.number(),
|
|
162
|
+
yaw: z.number(),
|
|
163
|
+
hp: z.number().int().nonnegative().max(100),
|
|
164
|
+
/** 护甲值 0–100;旧模型缺省 0。 */
|
|
165
|
+
armor: z.number().int().nonnegative().max(100).optional().default(0),
|
|
166
|
+
weapon: z.string().nullable(),
|
|
167
|
+
/** 当前帧真实持有道具;旧导出包缺省为空,UI 可回退到 loadout。 */
|
|
168
|
+
grenades: z.array(grenadeTypeSchema).optional().default([]),
|
|
169
|
+
alive: z.boolean(),
|
|
170
|
+
flashed: z.boolean(),
|
|
171
|
+
flashRemainingSeconds: z.number().nonnegative().optional().default(0),
|
|
172
|
+
hasDefuseKit: z.boolean(),
|
|
173
|
+
hasBomb: z.boolean(),
|
|
174
|
+
/** 本回合是否有头盔(player-economies 查表,非逐帧变化)。 */
|
|
175
|
+
hasHelmet: z.boolean().optional().default(false)
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
export const workspaceReplayPlayerSchema = z.object({
|
|
179
|
+
steamId64: z.string(),
|
|
180
|
+
name: z.string(),
|
|
181
|
+
teamKey: teamKeySchema,
|
|
182
|
+
side: sideSchema,
|
|
183
|
+
loadout: workspaceReplayLoadoutSchema.optional().default({
|
|
184
|
+
primaryWeapon: null,
|
|
185
|
+
secondaryWeapon: null,
|
|
186
|
+
grenadeCount: 0,
|
|
187
|
+
grenades: []
|
|
188
|
+
}),
|
|
189
|
+
frames: z.array(workspaceReplayFrameSchema)
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
export const workspaceKillEventSchema = z.object({
|
|
193
|
+
id: z.string(),
|
|
194
|
+
tick: z.number().int().positive(),
|
|
195
|
+
killerName: z.string().nullable(),
|
|
196
|
+
killerTeamKey: teamKeySchema.nullable(),
|
|
197
|
+
victimName: z.string(),
|
|
198
|
+
weapon: z.string(),
|
|
199
|
+
headshot: z.boolean(),
|
|
200
|
+
throughSmoke: z.boolean(),
|
|
201
|
+
noScope: z.boolean(),
|
|
202
|
+
flashAssist: z.boolean(),
|
|
203
|
+
tradeKill: z.boolean(),
|
|
204
|
+
wallbang: z.boolean(),
|
|
205
|
+
/** 击杀连线图层用的 world 坐标;旧模型缺省 null(连线图层降级隐藏)。 */
|
|
206
|
+
killerX: z.number().nullable().optional().default(null),
|
|
207
|
+
killerY: z.number().nullable().optional().default(null),
|
|
208
|
+
killerZ: z.number().nullable().optional().default(null),
|
|
209
|
+
victimX: z.number().nullable().optional().default(null),
|
|
210
|
+
victimY: z.number().nullable().optional().default(null),
|
|
211
|
+
victimZ: z.number().nullable().optional().default(null)
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
/** 回合内一颗道具的完整生命周期(world 坐标),来自 grenades.json。 */
|
|
215
|
+
export const workspaceReplayGrenadeSchema = z.object({
|
|
216
|
+
grenade: grenadeTypeSchema,
|
|
217
|
+
/** 投掷者在该回合的阵营;烟雾边框按 T/CT 着色,而不是固定 teamA/teamB。 */
|
|
218
|
+
throwerSide: sideSchema.nullable().optional().default(null),
|
|
219
|
+
throwTick: z.number().int().positive(),
|
|
220
|
+
effectTick: z.number().int().positive(),
|
|
221
|
+
/** 效果消失 tick;导出包缺失时 null,渲染端按道具类型取默认时长。 */
|
|
222
|
+
destroyTick: z.number().int().positive().nullable(),
|
|
223
|
+
throwX: z.number(),
|
|
224
|
+
throwY: z.number(),
|
|
225
|
+
effectX: z.number(),
|
|
226
|
+
effectY: z.number(),
|
|
227
|
+
/** 效果点 z 高度,双层地图按层过滤用;旧模型缺省 0(恒判上层兜底)。 */
|
|
228
|
+
effectZ: z.number().optional().default(0)
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
/** 道具飞行轨迹(v2.3+ 导出包才有),与玩家帧同一时间网格。 */
|
|
232
|
+
export const workspaceReplayProjectileSchema = z.object({
|
|
233
|
+
grenade: grenadeTypeSchema,
|
|
234
|
+
startTick: z.number().int().positive(),
|
|
235
|
+
x: z.array(z.number()),
|
|
236
|
+
y: z.array(z.number()),
|
|
237
|
+
/** 与 x/y 同长的 z 高度序列;旧模型缺省空数组。 */
|
|
238
|
+
z: z.array(z.number()).optional().default([])
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
/** C4 掉落期间(world 坐标):从 dropped 到被捡起/安放之间的地面标记。 */
|
|
242
|
+
export const workspaceReplayGroundBombSchema = z.object({
|
|
243
|
+
startTick: z.number().int().positive(),
|
|
244
|
+
endTick: z.number().int().positive(),
|
|
245
|
+
x: z.number(),
|
|
246
|
+
y: z.number(),
|
|
247
|
+
z: z.number().optional().default(0)
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
/** 拆弹器掉落期间(world 坐标):持 kit 的 CT 阵亡 → kit 落地,直到被捡起/回合结束。 */
|
|
251
|
+
export const workspaceReplayGroundDefuserSchema = z.object({
|
|
252
|
+
startTick: z.number().int().positive(),
|
|
253
|
+
endTick: z.number().int().positive(),
|
|
254
|
+
x: z.number(),
|
|
255
|
+
y: z.number(),
|
|
256
|
+
z: z.number().optional().default(0)
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
/** C4 事件锚点(world 坐标):plant 之后在落点定格显示。 */
|
|
260
|
+
export const workspaceReplayBombSchema = z.object({
|
|
261
|
+
plantTick: z.number().int().positive(),
|
|
262
|
+
x: z.number(),
|
|
263
|
+
y: z.number(),
|
|
264
|
+
z: z.number().optional().default(0),
|
|
265
|
+
defuseTick: z.number().int().positive().nullable(),
|
|
266
|
+
explodeTick: z.number().int().positive().nullable()
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
export const workspaceReplayRoundSchema = z.object({
|
|
270
|
+
roundNumber: z.number().int().positive(),
|
|
271
|
+
startTick: z.number().int().positive(),
|
|
272
|
+
/** 比赛钟开始倒计时的 freeze end tick。 */
|
|
273
|
+
freezeEndTick: z.number().int().positive(),
|
|
274
|
+
tickStep: z.number().int().positive(),
|
|
275
|
+
frameCount: z.number().int().nonnegative(),
|
|
276
|
+
players: z.array(workspaceReplayPlayerSchema),
|
|
277
|
+
kills: z.array(workspaceKillEventSchema),
|
|
278
|
+
grenades: z.array(workspaceReplayGrenadeSchema),
|
|
279
|
+
projectiles: z.array(workspaceReplayProjectileSchema),
|
|
280
|
+
/** 该回合未下包时 null。 */
|
|
281
|
+
bomb: workspaceReplayBombSchema.nullable(),
|
|
282
|
+
/** C4 掉落在地上的区间(掉落→捡起/安放)。数组可能为空。 */
|
|
283
|
+
groundBombs: z.array(workspaceReplayGroundBombSchema).default([]),
|
|
284
|
+
/** 拆弹器掉落在地上的区间(持 kit 的 CT 阵亡→被捡起/回合结束)。数组可能为空。 */
|
|
285
|
+
groundDefusers: z.array(workspaceReplayGroundDefuserSchema).default([]),
|
|
286
|
+
/** 回合官方结束 tick(rounds.json endTick)。旧缓存缺省。 */
|
|
287
|
+
officialEndTick: z.number().int().positive().optional(),
|
|
288
|
+
/** 回放目标结束 tick:优先使用下一回合 startTick,保留回合结束余韵但不拉到过长窗口。 */
|
|
289
|
+
targetEndTick: z.number().int().positive().optional()
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
export const workspaceReplaySchema = z.object({
|
|
293
|
+
available: z.boolean(),
|
|
294
|
+
sampleRate: z.number().int().positive().nullable(),
|
|
295
|
+
tickrate: z.number().int().positive().nullable(),
|
|
296
|
+
rounds: z.array(workspaceReplayRoundSchema),
|
|
297
|
+
capabilities: z.object({
|
|
298
|
+
hasDefuseKit: z.boolean()
|
|
299
|
+
})
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
export const matchWorkspaceModelSchema = z.object({
|
|
303
|
+
version: z.literal("cs2-demo-analysis-kit/workspace-0.1"),
|
|
304
|
+
sourceSchemaVersion: z.literal("cs2-demo-format/3.0"),
|
|
305
|
+
title: z.string(),
|
|
306
|
+
subtitle: z.string(),
|
|
307
|
+
scoreline: z.string(),
|
|
308
|
+
mapName: z.string(),
|
|
309
|
+
teams: analysisBundleSchema.shape.teams,
|
|
310
|
+
tabs: z.array(workspaceTabSchema),
|
|
311
|
+
overview: workspaceOverviewSchema,
|
|
312
|
+
scoreboard: z.array(playerScoreboardRowSchema),
|
|
313
|
+
rounds: z.array(workspaceRoundSchema),
|
|
314
|
+
players: z.array(workspacePlayerSchema),
|
|
315
|
+
economy: z.array(economyPointSchema),
|
|
316
|
+
weapons: z.array(workspaceWeaponRowSchema),
|
|
317
|
+
duels: workspaceDuelsSchema,
|
|
318
|
+
map: workspaceMapSchema,
|
|
319
|
+
replay: workspaceReplaySchema,
|
|
320
|
+
adminQa: qaReportSchema
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
export type WorkspaceTab = z.infer<typeof workspaceTabSchema>;
|
|
324
|
+
export type WorkspaceRoundFacets = z.infer<typeof workspaceRoundFacetsSchema>;
|
|
325
|
+
export type WorkspaceKpi = z.infer<typeof workspaceKpiSchema>;
|
|
326
|
+
export type WorkspaceRound = z.infer<typeof workspaceRoundSchema>;
|
|
327
|
+
export type WorkspaceWeaponRow = z.infer<typeof workspaceWeaponRowSchema>;
|
|
328
|
+
export type WorkspaceDuels = z.infer<typeof workspaceDuelsSchema>;
|
|
329
|
+
export type WorkspacePlayer = z.infer<typeof workspacePlayerSchema>;
|
|
330
|
+
export type WorkspaceSpatialPoint = z.infer<typeof workspaceSpatialPointSchema>;
|
|
331
|
+
export type WorkspaceReplayLoadout = z.infer<typeof workspaceReplayLoadoutSchema>;
|
|
332
|
+
export type WorkspaceReplayFrame = z.infer<typeof workspaceReplayFrameSchema>;
|
|
333
|
+
export type WorkspaceReplayPlayer = z.infer<typeof workspaceReplayPlayerSchema>;
|
|
334
|
+
export type WorkspaceKillEvent = z.infer<typeof workspaceKillEventSchema>;
|
|
335
|
+
export type WorkspaceReplayGrenade = z.infer<typeof workspaceReplayGrenadeSchema>;
|
|
336
|
+
export type WorkspaceReplayProjectile = z.infer<typeof workspaceReplayProjectileSchema>;
|
|
337
|
+
export type WorkspaceReplayGroundBomb = z.infer<typeof workspaceReplayGroundBombSchema>;
|
|
338
|
+
export type WorkspaceReplayGroundDefuser = z.infer<typeof workspaceReplayGroundDefuserSchema>;
|
|
339
|
+
export type WorkspaceReplayBomb = z.infer<typeof workspaceReplayBombSchema>;
|
|
340
|
+
export type WorkspaceReplayRound = z.infer<typeof workspaceReplayRoundSchema>;
|
|
341
|
+
export type MatchWorkspaceModel = z.infer<typeof matchWorkspaceModelSchema>;
|