@cs2dak/contract 1.0.0 → 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 +11 -5
- package/src/bracket.ts +68 -0
- package/src/cohort.ts +2 -1
- package/src/contract.test.ts +75 -10
- package/src/demo-package.ts +2 -2
- 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 +9 -0
- package/src/map-intelligence.ts +202 -0
- package/src/map-role.ts +317 -0
- package/src/player.ts +15 -4
- package/src/radar-field.ts +79 -0
- package/src/scoring.ts +9 -1
- package/src/trails.ts +56 -0
- package/src/upstream.ts +27 -7
- package/src/veto.ts +44 -0
- package/src/workspace.ts +184 -10
package/src/workspace.ts
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
export { seriesVetoSchema, type SeriesVeto } from "./veto.js";
|
|
2
3
|
import { teamKeySchema, sideSchema, grenadeTypeSchema, teamEconomySchema } from "cs2-demo-format";
|
|
3
4
|
import { qaReportSchema } from "./qa.js";
|
|
4
5
|
import {
|
|
5
6
|
analysisBundleSchema,
|
|
6
7
|
timelineEventSchema,
|
|
7
8
|
economyPointSchema,
|
|
8
|
-
heatmapPointSchema,
|
|
9
9
|
playerScoreboardRowSchema,
|
|
10
10
|
playerRoundFactSchema,
|
|
11
11
|
mapViewSchema,
|
|
12
12
|
} from "./analysis.js";
|
|
13
13
|
|
|
14
14
|
export const workspaceTabSchema = z.object({
|
|
15
|
-
key: z.enum(["overview", "rounds", "players", "economy", "map", "replay"]),
|
|
15
|
+
key: z.enum(["overview", "rounds", "players", "economy", "weapons", "duels", "map", "replay"]),
|
|
16
16
|
label: z.string()
|
|
17
17
|
});
|
|
18
18
|
|
|
@@ -28,6 +28,30 @@ export const workspaceOverviewSchema = z.object({
|
|
|
28
28
|
story: z.array(z.string())
|
|
29
29
|
});
|
|
30
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
|
+
|
|
31
55
|
export const workspaceRoundSchema = z.object({
|
|
32
56
|
roundNumber: z.number().int().positive(),
|
|
33
57
|
scoreBefore: z.string(),
|
|
@@ -37,7 +61,9 @@ export const workspaceRoundSchema = z.object({
|
|
|
37
61
|
teamAEconomy: teamEconomySchema,
|
|
38
62
|
teamBEconomy: teamEconomySchema,
|
|
39
63
|
events: z.array(timelineEventSchema),
|
|
40
|
-
playerFacts: z.array(playerRoundFactSchema)
|
|
64
|
+
playerFacts: z.array(playerRoundFactSchema),
|
|
65
|
+
/** 旧模型可缺省;缺省时回合筛选器自动隐藏对应维度。 */
|
|
66
|
+
facets: workspaceRoundFacetsSchema.optional()
|
|
41
67
|
});
|
|
42
68
|
|
|
43
69
|
export const workspacePlayerSchema = z.object({
|
|
@@ -45,13 +71,50 @@ export const workspacePlayerSchema = z.object({
|
|
|
45
71
|
teamName: z.string(),
|
|
46
72
|
summary: z.array(z.string()),
|
|
47
73
|
rrBreakdown: z.array(z.object({
|
|
48
|
-
key: z.enum(["combat", "trade", "clutch", "objective", "utility"]),
|
|
74
|
+
key: z.enum(["combat", "trade", "mapControl", "clutch", "objective", "utility"]),
|
|
49
75
|
label: z.string(),
|
|
50
76
|
value: z.number()
|
|
51
77
|
})),
|
|
52
78
|
roundFacts: z.array(playerRoundFactSchema)
|
|
53
79
|
});
|
|
54
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
|
+
|
|
55
118
|
export const workspaceMapModeSchema = z.object({
|
|
56
119
|
key: z.enum(["death", "kill", "grenade", "bomb", "position"]),
|
|
57
120
|
label: z.string(),
|
|
@@ -81,6 +144,16 @@ export const workspaceMapSchema = z.object({
|
|
|
81
144
|
})
|
|
82
145
|
});
|
|
83
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
|
+
|
|
84
157
|
export const workspaceReplayFrameSchema = z.object({
|
|
85
158
|
tick: z.number().int().positive(),
|
|
86
159
|
x: z.number(),
|
|
@@ -88,10 +161,18 @@ export const workspaceReplayFrameSchema = z.object({
|
|
|
88
161
|
z: z.number(),
|
|
89
162
|
yaw: z.number(),
|
|
90
163
|
hp: z.number().int().nonnegative().max(100),
|
|
164
|
+
/** 护甲值 0–100;旧模型缺省 0。 */
|
|
165
|
+
armor: z.number().int().nonnegative().max(100).optional().default(0),
|
|
91
166
|
weapon: z.string().nullable(),
|
|
167
|
+
/** 当前帧真实持有道具;旧导出包缺省为空,UI 可回退到 loadout。 */
|
|
168
|
+
grenades: z.array(grenadeTypeSchema).optional().default([]),
|
|
92
169
|
alive: z.boolean(),
|
|
93
170
|
flashed: z.boolean(),
|
|
94
|
-
|
|
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)
|
|
95
176
|
});
|
|
96
177
|
|
|
97
178
|
export const workspaceReplayPlayerSchema = z.object({
|
|
@@ -99,6 +180,12 @@ export const workspaceReplayPlayerSchema = z.object({
|
|
|
99
180
|
name: z.string(),
|
|
100
181
|
teamKey: teamKeySchema,
|
|
101
182
|
side: sideSchema,
|
|
183
|
+
loadout: workspaceReplayLoadoutSchema.optional().default({
|
|
184
|
+
primaryWeapon: null,
|
|
185
|
+
secondaryWeapon: null,
|
|
186
|
+
grenadeCount: 0,
|
|
187
|
+
grenades: []
|
|
188
|
+
}),
|
|
102
189
|
frames: z.array(workspaceReplayFrameSchema)
|
|
103
190
|
});
|
|
104
191
|
|
|
@@ -113,16 +200,93 @@ export const workspaceKillEventSchema = z.object({
|
|
|
113
200
|
throughSmoke: z.boolean(),
|
|
114
201
|
noScope: z.boolean(),
|
|
115
202
|
flashAssist: z.boolean(),
|
|
116
|
-
tradeKill: 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()
|
|
117
267
|
});
|
|
118
268
|
|
|
119
269
|
export const workspaceReplayRoundSchema = z.object({
|
|
120
270
|
roundNumber: z.number().int().positive(),
|
|
121
271
|
startTick: z.number().int().positive(),
|
|
272
|
+
/** 比赛钟开始倒计时的 freeze end tick。 */
|
|
273
|
+
freezeEndTick: z.number().int().positive(),
|
|
122
274
|
tickStep: z.number().int().positive(),
|
|
123
275
|
frameCount: z.number().int().nonnegative(),
|
|
124
276
|
players: z.array(workspaceReplayPlayerSchema),
|
|
125
|
-
kills: z.array(workspaceKillEventSchema)
|
|
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()
|
|
126
290
|
});
|
|
127
291
|
|
|
128
292
|
export const workspaceReplaySchema = z.object({
|
|
@@ -131,14 +295,13 @@ export const workspaceReplaySchema = z.object({
|
|
|
131
295
|
tickrate: z.number().int().positive().nullable(),
|
|
132
296
|
rounds: z.array(workspaceReplayRoundSchema),
|
|
133
297
|
capabilities: z.object({
|
|
134
|
-
hasDefuseKit: z.boolean()
|
|
135
|
-
hasBombPosition: z.boolean()
|
|
298
|
+
hasDefuseKit: z.boolean()
|
|
136
299
|
})
|
|
137
300
|
});
|
|
138
301
|
|
|
139
302
|
export const matchWorkspaceModelSchema = z.object({
|
|
140
303
|
version: z.literal("cs2-demo-analysis-kit/workspace-0.1"),
|
|
141
|
-
sourceSchemaVersion: z.literal("cs2-demo-format/
|
|
304
|
+
sourceSchemaVersion: z.literal("cs2-demo-format/3.0"),
|
|
142
305
|
title: z.string(),
|
|
143
306
|
subtitle: z.string(),
|
|
144
307
|
scoreline: z.string(),
|
|
@@ -150,18 +313,29 @@ export const matchWorkspaceModelSchema = z.object({
|
|
|
150
313
|
rounds: z.array(workspaceRoundSchema),
|
|
151
314
|
players: z.array(workspacePlayerSchema),
|
|
152
315
|
economy: z.array(economyPointSchema),
|
|
316
|
+
weapons: z.array(workspaceWeaponRowSchema),
|
|
317
|
+
duels: workspaceDuelsSchema,
|
|
153
318
|
map: workspaceMapSchema,
|
|
154
319
|
replay: workspaceReplaySchema,
|
|
155
320
|
adminQa: qaReportSchema
|
|
156
321
|
});
|
|
157
322
|
|
|
158
323
|
export type WorkspaceTab = z.infer<typeof workspaceTabSchema>;
|
|
324
|
+
export type WorkspaceRoundFacets = z.infer<typeof workspaceRoundFacetsSchema>;
|
|
159
325
|
export type WorkspaceKpi = z.infer<typeof workspaceKpiSchema>;
|
|
160
326
|
export type WorkspaceRound = z.infer<typeof workspaceRoundSchema>;
|
|
327
|
+
export type WorkspaceWeaponRow = z.infer<typeof workspaceWeaponRowSchema>;
|
|
328
|
+
export type WorkspaceDuels = z.infer<typeof workspaceDuelsSchema>;
|
|
161
329
|
export type WorkspacePlayer = z.infer<typeof workspacePlayerSchema>;
|
|
162
330
|
export type WorkspaceSpatialPoint = z.infer<typeof workspaceSpatialPointSchema>;
|
|
331
|
+
export type WorkspaceReplayLoadout = z.infer<typeof workspaceReplayLoadoutSchema>;
|
|
163
332
|
export type WorkspaceReplayFrame = z.infer<typeof workspaceReplayFrameSchema>;
|
|
164
333
|
export type WorkspaceReplayPlayer = z.infer<typeof workspaceReplayPlayerSchema>;
|
|
165
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>;
|
|
166
340
|
export type WorkspaceReplayRound = z.infer<typeof workspaceReplayRoundSchema>;
|
|
167
341
|
export type MatchWorkspaceModel = z.infer<typeof matchWorkspaceModelSchema>;
|