blanchon commited on
Commit
437403c
·
1 Parent(s): 27b5692

Minimap: read ticks.parquet instead of world.preview.jsonl

Browse files
Files changed (1) hide show
  1. src/lib/api/world.ts +39 -53
src/lib/api/world.ts CHANGED
@@ -1,5 +1,6 @@
1
  import type { PreviewChunk } from '$lib/types';
2
  import type { FetchOpts } from '$lib/api/hub';
 
3
 
4
  // One player's pose at one sampled tick. Round-relative `t` (seconds).
5
  export type WorldFrame = {
@@ -29,67 +30,55 @@ export type RoundWorld = {
29
 
30
  const cache = new Map<string, Promise<RoundWorld>>();
31
 
32
- // preview_video.src is ".../player=N/previews/chunk_NNN/preview.mp4" — strip
33
- // the filename to get the chunk dir we'll resolve sibling assets against.
34
  function chunkBaseUrl(chunk: PreviewChunk): string {
35
  return chunk.preview_video.src.replace(/\/[^/]+$/, '');
36
  }
37
 
38
- type WorldColumns = {
39
- t: number[];
40
- steamid: (number | string)[];
41
- name: string[];
42
- team_num: number[];
43
- is_alive: boolean[];
44
- health: number[];
45
- armor_value: number[];
46
- X: number[];
47
- Y: number[];
48
- Z: number[];
49
- };
50
 
51
- type InputsJson = {
52
- t: number[];
53
- view?: { yaw?: number[] };
 
 
 
 
 
 
54
  };
55
 
56
  async function loadChunkFrames(
57
  chunk: PreviewChunk,
58
- chunkStartT: number,
59
  player: number,
60
  opts: FetchOpts
61
  ): Promise<WorldFrame[]> {
62
  const base = chunkBaseUrl(chunk);
63
- const fetchFn = opts.fetch ?? fetch;
64
- const [worldR, inputsR] = await Promise.all([
65
- fetchFn(`${base}/world.preview.jsonl`, { signal: opts.signal }),
66
- fetchFn(`${base}/inputs.preview.json`, { signal: opts.signal })
67
- ]);
68
- if (!worldR.ok) return [];
69
- const worldText = await worldR.text();
70
- // JSONL with one columnar object on the first line.
71
- const firstLine = worldText.split('\n').find((l) => l.trim().length > 0);
72
- if (!firstLine) return [];
73
- const world = JSON.parse(firstLine) as WorldColumns;
74
- const inputs: InputsJson | null = inputsR.ok ? await inputsR.json() : null;
75
- const yawArr = inputs?.view?.yaw;
76
-
77
- const n = world.t.length;
78
- const frames: WorldFrame[] = new Array(n);
79
- for (let i = 0; i < n; i++) {
80
  frames[i] = {
81
- t: chunkStartT + world.t[i],
82
  player,
83
- steamid: String(world.steamid[i]),
84
- name: world.name[i],
85
- team_num: world.team_num[i],
86
- is_alive: world.is_alive[i],
87
- health: world.health[i],
88
- armor_value: world.armor_value[i],
89
- X: world.X[i],
90
- Y: world.Y[i],
91
- Z: world.Z[i],
92
- yaw: yawArr?.[i]
93
  };
94
  }
95
  return frames;
@@ -124,14 +113,11 @@ export async function loadRoundWorld(
124
 
125
  const players: PlayerSeries[] = await Promise.all(
126
  Array.from(byPlayer.entries()).map(async ([player, list]) => {
 
 
 
127
  list.sort((a, b) => a.chunk_index - b.chunk_index);
128
- let startT = 0;
129
- const frames: WorldFrame[] = [];
130
- for (const c of list) {
131
- const f = await loadChunkFrames(c, startT, player, opts);
132
- frames.push(...f);
133
- startT += c.duration_s || 0;
134
- }
135
  return { player, frames };
136
  })
137
  );
 
1
  import type { PreviewChunk } from '$lib/types';
2
  import type { FetchOpts } from '$lib/api/hub';
3
+ import { fetchParquetRows } from '$lib/api/parquet';
4
 
5
  // One player's pose at one sampled tick. Round-relative `t` (seconds).
6
  export type WorldFrame = {
 
30
 
31
  const cache = new Map<string, Promise<RoundWorld>>();
32
 
33
+ // preview_video.src is ".../round=R/player=N/video.preview.mp4" — strip the
34
+ // filename to get the per-POV dir we'll resolve sibling assets against.
35
  function chunkBaseUrl(chunk: PreviewChunk): string {
36
  return chunk.preview_video.src.replace(/\/[^/]+$/, '');
37
  }
38
 
39
+ // CS2 team_num values — matches the demo schema convention.
40
+ const TEAM_T = 2;
41
+ const TEAM_CT = 3;
 
 
 
 
 
 
 
 
 
42
 
43
+ type TickRow = {
44
+ t: number;
45
+ position_x: number;
46
+ position_y: number;
47
+ position_z: number;
48
+ view_yaw?: number;
49
+ health: number;
50
+ armor?: number;
51
+ is_alive: boolean;
52
  };
53
 
54
  async function loadChunkFrames(
55
  chunk: PreviewChunk,
 
56
  player: number,
57
  opts: FetchOpts
58
  ): Promise<WorldFrame[]> {
59
  const base = chunkBaseUrl(chunk);
60
+ const rows = await fetchParquetRows<TickRow>(`${base}/ticks.parquet`, opts);
61
+
62
+ const steamid = `slot-${player}`;
63
+ const name = `Player ${player}`;
64
+ const team_num = chunk.player_side === 'CT' ? TEAM_CT : TEAM_T;
65
+
66
+ const frames: WorldFrame[] = new Array(rows.length);
67
+ for (let i = 0; i < rows.length; i++) {
68
+ const r = rows[i];
 
 
 
 
 
 
 
 
69
  frames[i] = {
70
+ t: r.t,
71
  player,
72
+ steamid,
73
+ name,
74
+ team_num,
75
+ is_alive: r.is_alive,
76
+ health: r.health,
77
+ armor_value: r.armor ?? 0,
78
+ X: r.position_x,
79
+ Y: r.position_y,
80
+ Z: r.position_z,
81
+ yaw: r.view_yaw
82
  };
83
  }
84
  return frames;
 
113
 
114
  const players: PlayerSeries[] = await Promise.all(
115
  Array.from(byPlayer.entries()).map(async ([player, list]) => {
116
+ // opencs2_dataset has one ticks.parquet per (round, player); pick
117
+ // the lowest-chunk-index row defensively in case an upstream still
118
+ // emits multiple.
119
  list.sort((a, b) => a.chunk_index - b.chunk_index);
120
+ const frames = await loadChunkFrames(list[0], player, opts);
 
 
 
 
 
 
121
  return { player, frames };
122
  })
123
  );