t2-demo-parser 2.2.0 → 3.0.1

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.
Files changed (95) hide show
  1. package/README.md +170 -7
  2. package/dist/BitStream.d.ts +14 -3
  3. package/dist/BitStream.d.ts.map +1 -1
  4. package/dist/BitStream.js +28 -6
  5. package/dist/BitStream.js.map +1 -1
  6. package/dist/BitWriter.d.ts +29 -0
  7. package/dist/BitWriter.d.ts.map +1 -0
  8. package/dist/BitWriter.js +76 -0
  9. package/dist/BitWriter.js.map +1 -0
  10. package/dist/ClassRegistry.d.ts +23 -28
  11. package/dist/ClassRegistry.d.ts.map +1 -1
  12. package/dist/ClassRegistry.js +75 -84
  13. package/dist/ClassRegistry.js.map +1 -1
  14. package/dist/DataBlockParsers.d.ts.map +1 -1
  15. package/dist/DataBlockParsers.js +60 -85
  16. package/dist/DataBlockParsers.js.map +1 -1
  17. package/dist/DemoParser.d.ts +23 -22
  18. package/dist/DemoParser.d.ts.map +1 -1
  19. package/dist/DemoParser.js +218 -266
  20. package/dist/DemoParser.js.map +1 -1
  21. package/dist/EventParsers.d.ts.map +1 -1
  22. package/dist/EventParsers.js +87 -58
  23. package/dist/EventParsers.js.map +1 -1
  24. package/dist/GhostManager.d.ts +7 -1
  25. package/dist/GhostManager.d.ts.map +1 -1
  26. package/dist/GhostManager.js +231 -146
  27. package/dist/GhostManager.js.map +1 -1
  28. package/dist/HuffmanProcessor.d.ts +15 -0
  29. package/dist/HuffmanProcessor.d.ts.map +1 -1
  30. package/dist/HuffmanProcessor.js +45 -0
  31. package/dist/HuffmanProcessor.js.map +1 -1
  32. package/dist/LiveParser.d.ts +20 -1
  33. package/dist/LiveParser.d.ts.map +1 -1
  34. package/dist/LiveParser.js +31 -12
  35. package/dist/LiveParser.js.map +1 -1
  36. package/dist/PacketParser.d.ts +22 -2
  37. package/dist/PacketParser.d.ts.map +1 -1
  38. package/dist/PacketParser.js +111 -48
  39. package/dist/PacketParser.js.map +1 -1
  40. package/dist/Timeline.d.ts +5 -2
  41. package/dist/Timeline.d.ts.map +1 -1
  42. package/dist/Timeline.js +25 -12
  43. package/dist/Timeline.js.map +1 -1
  44. package/dist/cli.js +3 -0
  45. package/dist/cli.js.map +1 -1
  46. package/dist/dataBlockDataTypes.d.ts +118 -117
  47. package/dist/dataBlockDataTypes.d.ts.map +1 -1
  48. package/dist/dataBlockDataTypes.js.map +1 -1
  49. package/dist/defaultRegistry.d.ts +9 -0
  50. package/dist/defaultRegistry.d.ts.map +1 -0
  51. package/dist/defaultRegistry.js +43 -0
  52. package/dist/defaultRegistry.js.map +1 -0
  53. package/dist/eventDataTypes.d.ts +74 -72
  54. package/dist/eventDataTypes.d.ts.map +1 -1
  55. package/dist/eventDataTypes.js.map +1 -1
  56. package/dist/ghostDataTypes.d.ts +125 -131
  57. package/dist/ghostDataTypes.d.ts.map +1 -1
  58. package/dist/ghostDataTypes.js.map +1 -1
  59. package/dist/index.d.ts +6 -4
  60. package/dist/index.d.ts.map +1 -1
  61. package/dist/index.js +4 -2
  62. package/dist/index.js.map +1 -1
  63. package/dist/types.d.ts +51 -2
  64. package/dist/types.d.ts.map +1 -1
  65. package/dist/types.js +6 -0
  66. package/dist/types.js.map +1 -1
  67. package/package.json +4 -1
  68. package/dist/BitStream.test.d.ts +0 -2
  69. package/dist/BitStream.test.d.ts.map +0 -1
  70. package/dist/BitStream.test.js +0 -419
  71. package/dist/BitStream.test.js.map +0 -1
  72. package/dist/ClassRegistry.test.d.ts +0 -2
  73. package/dist/ClassRegistry.test.d.ts.map +0 -1
  74. package/dist/ClassRegistry.test.js +0 -83
  75. package/dist/ClassRegistry.test.js.map +0 -1
  76. package/dist/DataBlockParsers.test.d.ts +0 -2
  77. package/dist/DataBlockParsers.test.d.ts.map +0 -1
  78. package/dist/DataBlockParsers.test.js +0 -294
  79. package/dist/DataBlockParsers.test.js.map +0 -1
  80. package/dist/DemoParser.test.d.ts +0 -2
  81. package/dist/DemoParser.test.d.ts.map +0 -1
  82. package/dist/DemoParser.test.js +0 -472
  83. package/dist/DemoParser.test.js.map +0 -1
  84. package/dist/GhostStateAccumulator.test.d.ts +0 -2
  85. package/dist/GhostStateAccumulator.test.d.ts.map +0 -1
  86. package/dist/GhostStateAccumulator.test.js +0 -201
  87. package/dist/GhostStateAccumulator.test.js.map +0 -1
  88. package/dist/GhostTracker.test.d.ts +0 -2
  89. package/dist/GhostTracker.test.d.ts.map +0 -1
  90. package/dist/GhostTracker.test.js +0 -64
  91. package/dist/GhostTracker.test.js.map +0 -1
  92. package/dist/IncrementalParser.test.d.ts +0 -2
  93. package/dist/IncrementalParser.test.d.ts.map +0 -1
  94. package/dist/IncrementalParser.test.js +0 -226
  95. package/dist/IncrementalParser.test.js.map +0 -1
package/dist/Timeline.js CHANGED
@@ -1,25 +1,35 @@
1
- import { BlockTypePacket } from "./types.js";
1
+ import { BlockTypePacket, BlockTypeMove, MoveTickMs } from "./types.js";
2
2
  // ============================================================
3
3
  // Timeline builder
4
4
  // ============================================================
5
5
  /**
6
6
  * Extracts a time-indexed DemoTimeline from a parsed DemoFile.
7
7
  *
8
- * Timestamps are derived by distributing packet blocks evenly across
9
- * the demo duration (Torque sends packets at a fixed tick rate).
8
+ * Each Move block is one fixed 32ms simulation tick, so a packet's
9
+ * timestamp is the number of Move blocks that precede it × 32ms. This is
10
+ * exact demo time (the recorder writes a Move block every tick whether or
11
+ * not a packet arrived), and it matches `DemoParser.bufferedMoveTicks`.
10
12
  */
11
13
  export function buildTimeline(demo, registry) {
12
14
  const packetBlocks = [];
15
+ let ticks = 0;
13
16
  for (const block of demo.blocks) {
14
- if (block.type === BlockTypePacket &&
17
+ if (block.type === BlockTypeMove) {
18
+ ticks++;
19
+ }
20
+ else if (block.type === BlockTypePacket &&
15
21
  block.parsed &&
16
22
  "dnetHeader" in block.parsed) {
17
- packetBlocks.push({ block, pkt: block.parsed });
23
+ packetBlocks.push({
24
+ block,
25
+ pkt: block.parsed,
26
+ time: ticks * MoveTickMs,
27
+ });
18
28
  }
19
29
  }
20
30
  const packetCount = packetBlocks.length;
21
31
  const durationMs = demo.header.demoLengthMs;
22
- const tickIntervalMs = packetCount > 1 ? durationMs / (packetCount - 1) : 0;
32
+ const tickIntervalMs = MoveTickMs;
23
33
  // Track active ghost instances (ghostIndex → current instance)
24
34
  const activeGhosts = new Map();
25
35
  const allInstances = [];
@@ -45,8 +55,7 @@ export function buildTimeline(demo, registry) {
45
55
  }
46
56
  }
47
57
  for (let i = 0; i < packetBlocks.length; i++) {
48
- const time = i * tickIntervalMs;
49
- const { pkt } = packetBlocks[i];
58
+ const { pkt, time } = packetBlocks[i];
50
59
  // --- Control object position ---
51
60
  if (pkt.gameState.compressionPoint || pkt.gameState.controlObjectData) {
52
61
  const coData = pkt.gameState.controlObjectData;
@@ -127,14 +136,18 @@ export function buildTimeline(demo, registry) {
127
136
  }
128
137
  /**
129
138
  * Check if a Vec3 position is within reasonable game world bounds.
130
- * Rejects IEEE 754 denormalized values (e.g., 1.66e-34) that indicate
131
- * garbage data from bit stream misalignment.
139
+ * Rejects non-finite values, anything beyond the largest terrain, and
140
+ * near-zero magnitudes (e.g. 1.66e-34) that only arise from
141
+ * misaligned bit reads — no real coordinate is below a micrometre
142
+ * unless it is exactly zero.
132
143
  */
144
+ const MaxWorldCoordinate = 50000;
145
+ const MinNonZeroCoordinate = 1e-6;
133
146
  function isValidPosition(pos) {
134
147
  const vals = [pos.x, pos.y, pos.z];
135
148
  return vals.every((v) => Number.isFinite(v) &&
136
- Math.abs(v) < 50000 &&
137
- (Math.abs(v) >= 0.01 || v === 0));
149
+ Math.abs(v) < MaxWorldCoordinate &&
150
+ (Math.abs(v) >= MinNonZeroCoordinate || v === 0));
138
151
  }
139
152
  function isVec3(v) {
140
153
  if (v === null || typeof v !== "object")
@@ -1 +1 @@
1
- {"version":3,"file":"Timeline.js","sourceRoot":"","sources":["../src/Timeline.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n DemoFile,\n DemoBlock,\n PacketData,\n GhostUpdate,\n NetEventInfo,\n} from \"./types.js\";\nimport { BlockTypePacket } from \"./types.js\";\nimport type { ClassRegistry, ParsedData } from \"./ClassRegistry.js\";\nimport type { Vec3, Quat } from \"./dataTypes.js\";\n\nexport type { Vec3, Quat };\n\n// ============================================================\n// Timeline data structures for Three.js animation output\n// ============================================================\n\n/** A single keyframe for a ghost object. */\nexport interface GhostKeyframe {\n time: number;\n position?: Vec3;\n rotation?: Quat | Vec3;\n velocity?: Vec3;\n data?: ParsedData;\n}\n\n/** A continuous lifecycle segment of a ghost (from create to delete). */\nexport interface GhostInstance {\n ghostIndex: number;\n classId: number;\n className: string;\n spawnTime: number;\n despawnTime?: number;\n keyframes: GhostKeyframe[];\n}\n\n/** Keyframe for the control object (recording player). */\nexport interface ControlObjectKeyframe {\n time: number;\n ghostIndex: number;\n position?: Vec3;\n velocity?: Vec3;\n data?: ParsedData;\n}\n\n/** A timestamped game event. */\nexport interface GameEvent {\n time: number;\n type: string;\n classId: number;\n guaranteed: boolean;\n data?: ParsedData;\n}\n\n/** Complete timeline extracted from a demo file. */\nexport interface DemoTimeline {\n durationMs: number;\n tickIntervalMs: number;\n packetCount: number;\n ghostInstances: GhostInstance[];\n controlObject: ControlObjectKeyframe[];\n events: GameEvent[];\n}\n\n// ============================================================\n// Timeline builder\n// ============================================================\n\n/**\n * Extracts a time-indexed DemoTimeline from a parsed DemoFile.\n *\n * Timestamps are derived by distributing packet blocks evenly across\n * the demo duration (Torque sends packets at a fixed tick rate).\n */\nexport function buildTimeline(\n demo: DemoFile,\n registry: ClassRegistry\n): DemoTimeline {\n const packetBlocks: { block: DemoBlock; pkt: PacketData }[] = [];\n\n for (const block of demo.blocks) {\n if (\n block.type === BlockTypePacket &&\n block.parsed &&\n \"dnetHeader\" in block.parsed\n ) {\n packetBlocks.push({ block, pkt: block.parsed });\n }\n }\n\n const packetCount = packetBlocks.length;\n const durationMs = demo.header.demoLengthMs;\n const tickIntervalMs = packetCount > 1 ? durationMs / (packetCount - 1) : 0;\n\n // Track active ghost instances (ghostIndex → current instance)\n const activeGhosts = new Map<number, GhostInstance>();\n const allInstances: GhostInstance[] = [];\n const controlObject: ControlObjectKeyframe[] = [];\n const events: GameEvent[] = [];\n\n // Seed active ghosts from the initial block — these ghosts were created\n // before recording started and only receive updates in the packet stream.\n for (const ghost of demo.initialBlock.initialGhosts) {\n if (ghost.type === \"create\" && ghost.classId !== undefined) {\n const parserEntry = registry.getGhostParser(ghost.classId);\n const instance: GhostInstance = {\n ghostIndex: ghost.index,\n classId: ghost.classId,\n className: parserEntry?.name ?? `ghost_${ghost.classId}`,\n spawnTime: 0,\n keyframes: [],\n };\n if (ghost.parsedData) {\n instance.keyframes.push(makeGhostKeyframe(0, ghost.parsedData));\n }\n activeGhosts.set(ghost.index, instance);\n allInstances.push(instance);\n }\n }\n\n for (let i = 0; i < packetBlocks.length; i++) {\n const time = i * tickIntervalMs;\n const { pkt } = packetBlocks[i];\n\n // --- Control object position ---\n if (pkt.gameState.compressionPoint || pkt.gameState.controlObjectData) {\n const coData = pkt.gameState.controlObjectData;\n const coPos = coData?.position;\n const rawPosition = isVec3(coPos)\n ? coPos\n : pkt.gameState.compressionPoint;\n const position =\n rawPosition && isValidPosition(rawPosition) ? rawPosition : undefined;\n\n if (position) {\n const coVel = coData?.velocity;\n controlObject.push({\n time,\n ghostIndex: pkt.gameState.controlObjectGhostIndex ?? -1,\n position,\n velocity: isVec3(coVel) ? coVel : undefined,\n data: coData,\n });\n }\n }\n\n // --- Events ---\n for (const evt of pkt.events) {\n const parserEntry = registry.getEventParser(evt.classId);\n events.push({\n time,\n type: parserEntry?.name ?? `event_${evt.classId}`,\n classId: evt.classId,\n guaranteed: evt.guaranteed,\n data: evt.parsedData,\n });\n }\n\n // --- Ghost updates ---\n for (const ghost of pkt.ghosts) {\n if (ghost.type === \"create\" && ghost.classId !== undefined) {\n // End any existing instance at this index (shouldn't happen often)\n const prev = activeGhosts.get(ghost.index);\n if (prev && prev.despawnTime === undefined) {\n prev.despawnTime = time;\n }\n\n const parserEntry =\n ghost.classId !== undefined\n ? registry.getGhostParser(ghost.classId)\n : undefined;\n\n const instance: GhostInstance = {\n ghostIndex: ghost.index,\n classId: ghost.classId,\n className: parserEntry?.name ?? `ghost_${ghost.classId}`,\n spawnTime: time,\n keyframes: [],\n };\n\n if (ghost.parsedData) {\n instance.keyframes.push(\n makeGhostKeyframe(time, ghost.parsedData)\n );\n }\n\n activeGhosts.set(ghost.index, instance);\n allInstances.push(instance);\n } else if (ghost.type === \"update\") {\n const instance = activeGhosts.get(ghost.index);\n if (instance && ghost.parsedData) {\n instance.keyframes.push(\n makeGhostKeyframe(time, ghost.parsedData)\n );\n }\n } else if (ghost.type === \"delete\") {\n const instance = activeGhosts.get(ghost.index);\n if (instance) {\n instance.despawnTime = time;\n activeGhosts.delete(ghost.index);\n }\n }\n }\n }\n\n return {\n durationMs,\n tickIntervalMs,\n packetCount,\n ghostInstances: allInstances,\n controlObject,\n events,\n };\n}\n\n/**\n * Check if a Vec3 position is within reasonable game world bounds.\n * Rejects IEEE 754 denormalized values (e.g., 1.66e-34) that indicate\n * garbage data from bit stream misalignment.\n */\nfunction isValidPosition(pos: Vec3): boolean {\n const vals = [pos.x, pos.y, pos.z];\n return vals.every(\n (v) =>\n Number.isFinite(v) &&\n Math.abs(v) < 50000 &&\n (Math.abs(v) >= 0.01 || v === 0)\n );\n}\n\nfunction isVec3(v: unknown): v is Vec3 {\n if (v === null || typeof v !== \"object\") return false;\n const o = v as ParsedData;\n return (\n typeof o.x === \"number\" &&\n typeof o.y === \"number\" &&\n typeof o.z === \"number\"\n );\n}\n\n/** Extract position/rotation/velocity from parsed ghost data into a keyframe. */\nfunction makeGhostKeyframe(\n time: number,\n data: ParsedData\n): GhostKeyframe {\n const kf: GhostKeyframe = { time };\n\n if (isVec3(data.position) && isValidPosition(data.position)) {\n kf.position = data.position;\n }\n if (isVec3(data.rotation)) {\n // Rotation may be Vec3 (euler) or Quat (with w).\n kf.rotation = data.rotation;\n }\n if (isVec3(data.velocity)) {\n kf.velocity = data.velocity;\n }\n\n // Store full data for consumers that need more detail\n kf.data = data;\n\n return kf;\n}\n\n// ============================================================\n// Timeline summary / stats\n// ============================================================\n\nexport interface TimelineStats {\n durationMs: number;\n tickIntervalMs: number;\n packetCount: number;\n controlObjectKeyframes: number;\n totalGhostInstances: number;\n ghostInstancesByClass: Map<string, number>;\n ghostKeyframesByClass: Map<string, number>;\n ghostsWithPosition: number;\n totalEvents: number;\n eventsByType: Map<string, number>;\n}\n\nexport function getTimelineStats(timeline: DemoTimeline): TimelineStats {\n const ghostInstancesByClass = new Map<string, number>();\n const ghostKeyframesByClass = new Map<string, number>();\n let ghostsWithPosition = 0;\n\n for (const inst of timeline.ghostInstances) {\n ghostInstancesByClass.set(\n inst.className,\n (ghostInstancesByClass.get(inst.className) || 0) + 1\n );\n ghostKeyframesByClass.set(\n inst.className,\n (ghostKeyframesByClass.get(inst.className) || 0) + inst.keyframes.length\n );\n if (inst.keyframes.some((kf) => kf.position !== undefined)) {\n ghostsWithPosition++;\n }\n }\n\n const eventsByType = new Map<string, number>();\n for (const evt of timeline.events) {\n eventsByType.set(evt.type, (eventsByType.get(evt.type) || 0) + 1);\n }\n\n return {\n durationMs: timeline.durationMs,\n tickIntervalMs: timeline.tickIntervalMs,\n packetCount: timeline.packetCount,\n controlObjectKeyframes: timeline.controlObject.length,\n totalGhostInstances: timeline.ghostInstances.length,\n ghostInstancesByClass,\n ghostKeyframesByClass,\n ghostsWithPosition,\n totalEvents: timeline.events.length,\n eventsByType,\n };\n}\n\n// ============================================================\n// JSON export for Three.js consumption\n// ============================================================\n\n/** Slimmed-down ghost instance for JSON export (no raw data blobs). */\ninterface ExportGhostKeyframe {\n t: number;\n p?: [number, number, number];\n r?: [number, number, number] | [number, number, number, number];\n v?: [number, number, number];\n}\n\ninterface ExportGhostInstance {\n ghostIndex: number;\n classId: number;\n className: string;\n spawnTime: number;\n despawnTime?: number;\n keyframes: ExportGhostKeyframe[];\n}\n\ninterface ExportControlKeyframe {\n t: number;\n p?: [number, number, number];\n v?: [number, number, number];\n}\n\nexport interface ExportTimeline {\n durationMs: number;\n tickIntervalMs: number;\n controlObject: ExportControlKeyframe[];\n ghosts: ExportGhostInstance[];\n events: GameEvent[];\n}\n\n/**\n * Convert a DemoTimeline to a compact JSON-serializable format\n * suitable for Three.js consumption. Positions/rotations are\n * packed into arrays, and only ghost instances with position\n * data are included.\n */\nexport function exportTimeline(timeline: DemoTimeline): ExportTimeline {\n const controlObject: ExportControlKeyframe[] = timeline.controlObject\n .filter((co) => co.position !== undefined)\n .map((co) => {\n const kf: ExportControlKeyframe = { t: Math.round(co.time) };\n if (co.position)\n kf.p = [co.position.x, co.position.y, co.position.z];\n if (co.velocity) kf.v = [co.velocity.x, co.velocity.y, co.velocity.z];\n return kf;\n });\n\n const ghosts: ExportGhostInstance[] = [];\n for (const inst of timeline.ghostInstances) {\n // Only export instances that have at least one valid position keyframe\n const posKeyframes = inst.keyframes.filter(\n (kf) => kf.position !== undefined\n );\n if (posKeyframes.length === 0) continue;\n\n const keyframes: ExportGhostKeyframe[] = posKeyframes.map((kf) => {\n const ekf: ExportGhostKeyframe = { t: Math.round(kf.time) };\n if (kf.position)\n ekf.p = [kf.position.x, kf.position.y, kf.position.z];\n if (kf.rotation) {\n const r = kf.rotation;\n ekf.r =\n \"w\" in r\n ? [r.x, r.y, r.z, r.w]\n : [r.x, r.y, r.z];\n }\n if (kf.velocity)\n ekf.v = [kf.velocity.x, kf.velocity.y, kf.velocity.z];\n return ekf;\n });\n if (keyframes.length === 0) continue;\n\n ghosts.push({\n ghostIndex: inst.ghostIndex,\n classId: inst.classId,\n className: inst.className,\n spawnTime: Math.round(inst.spawnTime),\n despawnTime:\n inst.despawnTime !== undefined\n ? Math.round(inst.despawnTime)\n : undefined,\n keyframes,\n });\n }\n\n return {\n durationMs: timeline.durationMs,\n tickIntervalMs: timeline.tickIntervalMs,\n controlObject,\n ghosts,\n events: timeline.events,\n };\n}\n"]}
1
+ {"version":3,"file":"Timeline.js","sourceRoot":"","sources":["../src/Timeline.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n DemoFile,\n DemoBlock,\n PacketData,\n GhostUpdate,\n NetEventInfo,\n} from \"./types.js\";\nimport { BlockTypePacket, BlockTypeMove, MoveTickMs } from \"./types.js\";\nimport type { ClassRegistry, ParsedData } from \"./ClassRegistry.js\";\nimport type { Vec3, Quat } from \"./dataTypes.js\";\n\nexport type { Vec3, Quat };\n\n// ============================================================\n// Timeline data structures for Three.js animation output\n// ============================================================\n\n/** A single keyframe for a ghost object. */\nexport interface GhostKeyframe {\n time: number;\n position?: Vec3;\n rotation?: Quat | Vec3;\n velocity?: Vec3;\n data?: ParsedData;\n}\n\n/** A continuous lifecycle segment of a ghost (from create to delete). */\nexport interface GhostInstance {\n ghostIndex: number;\n classId: number;\n className: string;\n spawnTime: number;\n despawnTime?: number;\n keyframes: GhostKeyframe[];\n}\n\n/** Keyframe for the control object (recording player). */\nexport interface ControlObjectKeyframe {\n time: number;\n ghostIndex: number;\n position?: Vec3;\n velocity?: Vec3;\n data?: ParsedData;\n}\n\n/** A timestamped game event. */\nexport interface GameEvent {\n time: number;\n type: string;\n classId: number;\n guaranteed: boolean;\n data?: ParsedData;\n}\n\n/** Complete timeline extracted from a demo file. */\nexport interface DemoTimeline {\n durationMs: number;\n /** Simulation tick length (MoveTickMs); keyframe times are multiples of it. */\n tickIntervalMs: number;\n packetCount: number;\n ghostInstances: GhostInstance[];\n controlObject: ControlObjectKeyframe[];\n events: GameEvent[];\n}\n\n// ============================================================\n// Timeline builder\n// ============================================================\n\n/**\n * Extracts a time-indexed DemoTimeline from a parsed DemoFile.\n *\n * Each Move block is one fixed 32ms simulation tick, so a packet's\n * timestamp is the number of Move blocks that precede it × 32ms. This is\n * exact demo time (the recorder writes a Move block every tick whether or\n * not a packet arrived), and it matches `DemoParser.bufferedMoveTicks`.\n */\nexport function buildTimeline(\n demo: DemoFile,\n registry: ClassRegistry\n): DemoTimeline {\n const packetBlocks: { block: DemoBlock; pkt: PacketData; time: number }[] =\n [];\n\n let ticks = 0;\n for (const block of demo.blocks) {\n if (block.type === BlockTypeMove) {\n ticks++;\n } else if (\n block.type === BlockTypePacket &&\n block.parsed &&\n \"dnetHeader\" in block.parsed\n ) {\n packetBlocks.push({\n block,\n pkt: block.parsed,\n time: ticks * MoveTickMs,\n });\n }\n }\n\n const packetCount = packetBlocks.length;\n const durationMs = demo.header.demoLengthMs;\n const tickIntervalMs = MoveTickMs;\n\n // Track active ghost instances (ghostIndex → current instance)\n const activeGhosts = new Map<number, GhostInstance>();\n const allInstances: GhostInstance[] = [];\n const controlObject: ControlObjectKeyframe[] = [];\n const events: GameEvent[] = [];\n\n // Seed active ghosts from the initial block — these ghosts were created\n // before recording started and only receive updates in the packet stream.\n for (const ghost of demo.initialBlock.initialGhosts) {\n if (ghost.type === \"create\" && ghost.classId !== undefined) {\n const parserEntry = registry.getGhostParser(ghost.classId);\n const instance: GhostInstance = {\n ghostIndex: ghost.index,\n classId: ghost.classId,\n className: parserEntry?.name ?? `ghost_${ghost.classId}`,\n spawnTime: 0,\n keyframes: [],\n };\n if (ghost.parsedData) {\n instance.keyframes.push(makeGhostKeyframe(0, ghost.parsedData));\n }\n activeGhosts.set(ghost.index, instance);\n allInstances.push(instance);\n }\n }\n\n for (let i = 0; i < packetBlocks.length; i++) {\n const { pkt, time } = packetBlocks[i];\n\n // --- Control object position ---\n if (pkt.gameState.compressionPoint || pkt.gameState.controlObjectData) {\n const coData = pkt.gameState.controlObjectData;\n const coPos = coData?.position;\n const rawPosition = isVec3(coPos)\n ? coPos\n : pkt.gameState.compressionPoint;\n const position =\n rawPosition && isValidPosition(rawPosition) ? rawPosition : undefined;\n\n if (position) {\n const coVel = coData?.velocity;\n controlObject.push({\n time,\n ghostIndex: pkt.gameState.controlObjectGhostIndex ?? -1,\n position,\n velocity: isVec3(coVel) ? coVel : undefined,\n data: coData,\n });\n }\n }\n\n // --- Events ---\n for (const evt of pkt.events) {\n const parserEntry = registry.getEventParser(evt.classId);\n events.push({\n time,\n type: parserEntry?.name ?? `event_${evt.classId}`,\n classId: evt.classId,\n guaranteed: evt.guaranteed,\n data: evt.parsedData,\n });\n }\n\n // --- Ghost updates ---\n for (const ghost of pkt.ghosts) {\n if (ghost.type === \"create\" && ghost.classId !== undefined) {\n // End any existing instance at this index (shouldn't happen often)\n const prev = activeGhosts.get(ghost.index);\n if (prev && prev.despawnTime === undefined) {\n prev.despawnTime = time;\n }\n\n const parserEntry =\n ghost.classId !== undefined\n ? registry.getGhostParser(ghost.classId)\n : undefined;\n\n const instance: GhostInstance = {\n ghostIndex: ghost.index,\n classId: ghost.classId,\n className: parserEntry?.name ?? `ghost_${ghost.classId}`,\n spawnTime: time,\n keyframes: [],\n };\n\n if (ghost.parsedData) {\n instance.keyframes.push(\n makeGhostKeyframe(time, ghost.parsedData)\n );\n }\n\n activeGhosts.set(ghost.index, instance);\n allInstances.push(instance);\n } else if (ghost.type === \"update\") {\n const instance = activeGhosts.get(ghost.index);\n if (instance && ghost.parsedData) {\n instance.keyframes.push(\n makeGhostKeyframe(time, ghost.parsedData)\n );\n }\n } else if (ghost.type === \"delete\") {\n const instance = activeGhosts.get(ghost.index);\n if (instance) {\n instance.despawnTime = time;\n activeGhosts.delete(ghost.index);\n }\n }\n }\n }\n\n return {\n durationMs,\n tickIntervalMs,\n packetCount,\n ghostInstances: allInstances,\n controlObject,\n events,\n };\n}\n\n/**\n * Check if a Vec3 position is within reasonable game world bounds.\n * Rejects non-finite values, anything beyond the largest terrain, and\n * near-zero magnitudes (e.g. 1.66e-34) that only arise from\n * misaligned bit reads — no real coordinate is below a micrometre\n * unless it is exactly zero.\n */\nconst MaxWorldCoordinate = 50000;\nconst MinNonZeroCoordinate = 1e-6;\n\nfunction isValidPosition(pos: Vec3): boolean {\n const vals = [pos.x, pos.y, pos.z];\n return vals.every(\n (v) =>\n Number.isFinite(v) &&\n Math.abs(v) < MaxWorldCoordinate &&\n (Math.abs(v) >= MinNonZeroCoordinate || v === 0)\n );\n}\n\nfunction isVec3(v: unknown): v is Vec3 {\n if (v === null || typeof v !== \"object\") return false;\n const o = v as ParsedData;\n return (\n typeof o.x === \"number\" &&\n typeof o.y === \"number\" &&\n typeof o.z === \"number\"\n );\n}\n\n/** Extract position/rotation/velocity from parsed ghost data into a keyframe. */\nfunction makeGhostKeyframe(\n time: number,\n data: ParsedData\n): GhostKeyframe {\n const kf: GhostKeyframe = { time };\n\n if (isVec3(data.position) && isValidPosition(data.position)) {\n kf.position = data.position;\n }\n if (isVec3(data.rotation)) {\n // Rotation may be Vec3 (euler) or Quat (with w).\n kf.rotation = data.rotation;\n }\n if (isVec3(data.velocity)) {\n kf.velocity = data.velocity;\n }\n\n // Store full data for consumers that need more detail\n kf.data = data;\n\n return kf;\n}\n\n// ============================================================\n// Timeline summary / stats\n// ============================================================\n\nexport interface TimelineStats {\n durationMs: number;\n tickIntervalMs: number;\n packetCount: number;\n controlObjectKeyframes: number;\n totalGhostInstances: number;\n ghostInstancesByClass: Map<string, number>;\n ghostKeyframesByClass: Map<string, number>;\n ghostsWithPosition: number;\n totalEvents: number;\n eventsByType: Map<string, number>;\n}\n\nexport function getTimelineStats(timeline: DemoTimeline): TimelineStats {\n const ghostInstancesByClass = new Map<string, number>();\n const ghostKeyframesByClass = new Map<string, number>();\n let ghostsWithPosition = 0;\n\n for (const inst of timeline.ghostInstances) {\n ghostInstancesByClass.set(\n inst.className,\n (ghostInstancesByClass.get(inst.className) || 0) + 1\n );\n ghostKeyframesByClass.set(\n inst.className,\n (ghostKeyframesByClass.get(inst.className) || 0) + inst.keyframes.length\n );\n if (inst.keyframes.some((kf) => kf.position !== undefined)) {\n ghostsWithPosition++;\n }\n }\n\n const eventsByType = new Map<string, number>();\n for (const evt of timeline.events) {\n eventsByType.set(evt.type, (eventsByType.get(evt.type) || 0) + 1);\n }\n\n return {\n durationMs: timeline.durationMs,\n tickIntervalMs: timeline.tickIntervalMs,\n packetCount: timeline.packetCount,\n controlObjectKeyframes: timeline.controlObject.length,\n totalGhostInstances: timeline.ghostInstances.length,\n ghostInstancesByClass,\n ghostKeyframesByClass,\n ghostsWithPosition,\n totalEvents: timeline.events.length,\n eventsByType,\n };\n}\n\n// ============================================================\n// JSON export for Three.js consumption\n// ============================================================\n\n/** Slimmed-down ghost instance for JSON export (no raw data blobs). */\ninterface ExportGhostKeyframe {\n t: number;\n p?: [number, number, number];\n r?: [number, number, number] | [number, number, number, number];\n v?: [number, number, number];\n}\n\ninterface ExportGhostInstance {\n ghostIndex: number;\n classId: number;\n className: string;\n spawnTime: number;\n despawnTime?: number;\n keyframes: ExportGhostKeyframe[];\n}\n\ninterface ExportControlKeyframe {\n t: number;\n p?: [number, number, number];\n v?: [number, number, number];\n}\n\nexport interface ExportTimeline {\n durationMs: number;\n tickIntervalMs: number;\n controlObject: ExportControlKeyframe[];\n ghosts: ExportGhostInstance[];\n events: GameEvent[];\n}\n\n/**\n * Convert a DemoTimeline to a compact JSON-serializable format\n * suitable for Three.js consumption. Positions/rotations are\n * packed into arrays, and only ghost instances with position\n * data are included.\n */\nexport function exportTimeline(timeline: DemoTimeline): ExportTimeline {\n const controlObject: ExportControlKeyframe[] = timeline.controlObject\n .filter((co) => co.position !== undefined)\n .map((co) => {\n const kf: ExportControlKeyframe = { t: Math.round(co.time) };\n if (co.position)\n kf.p = [co.position.x, co.position.y, co.position.z];\n if (co.velocity) kf.v = [co.velocity.x, co.velocity.y, co.velocity.z];\n return kf;\n });\n\n const ghosts: ExportGhostInstance[] = [];\n for (const inst of timeline.ghostInstances) {\n // Only export instances that have at least one valid position keyframe\n const posKeyframes = inst.keyframes.filter(\n (kf) => kf.position !== undefined\n );\n if (posKeyframes.length === 0) continue;\n\n const keyframes: ExportGhostKeyframe[] = posKeyframes.map((kf) => {\n const ekf: ExportGhostKeyframe = { t: Math.round(kf.time) };\n if (kf.position)\n ekf.p = [kf.position.x, kf.position.y, kf.position.z];\n if (kf.rotation) {\n const r = kf.rotation;\n ekf.r =\n \"w\" in r\n ? [r.x, r.y, r.z, r.w]\n : [r.x, r.y, r.z];\n }\n if (kf.velocity)\n ekf.v = [kf.velocity.x, kf.velocity.y, kf.velocity.z];\n return ekf;\n });\n if (keyframes.length === 0) continue;\n\n ghosts.push({\n ghostIndex: inst.ghostIndex,\n classId: inst.classId,\n className: inst.className,\n spawnTime: Math.round(inst.spawnTime),\n despawnTime:\n inst.despawnTime !== undefined\n ? Math.round(inst.despawnTime)\n : undefined,\n keyframes,\n });\n }\n\n return {\n durationMs: timeline.durationMs,\n tickIntervalMs: timeline.tickIntervalMs,\n controlObject,\n ghosts,\n events: timeline.events,\n };\n}\n"]}
package/dist/cli.js CHANGED
@@ -59,6 +59,9 @@ console.log();
59
59
  console.log("=== Summary ===");
60
60
  console.log(`Protocol version: 0x${demo.header.protocolVersion.toString(16)}`);
61
61
  console.log(`Demo length: ${demo.header.demoLengthMs}ms (${formatTime(demo.header.demoLengthMs)})`);
62
+ for (const warning of demo.initialBlock.warnings) {
63
+ console.log(`WARNING (initial block): ${warning}`);
64
+ }
62
65
  const moveBlocks = demo.blocks.filter((b) => b.type === BlockTypeMove);
63
66
  const infoBlocks = demo.blocks.filter((b) => b.type === BlockTypeInfo);
64
67
  const sendPacketBlocks = demo.blocks.filter((b) => b.type === BlockTypeSendPacket);
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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","sourcesContent":["#!/usr/bin/env node\nimport * as fs from \"fs\";\nimport * as path from \"path\";\nimport { DemoParser } from \"./DemoParser.js\";\nimport {\n BlockTypePacket,\n BlockTypeSendPacket,\n BlockTypeMove,\n BlockTypeInfo,\n} from \"./types.js\";\nimport type { DemoBlock, PacketData } from \"./types.js\";\nimport {\n buildTimeline,\n getTimelineStats,\n exportTimeline,\n} from \"./Timeline.js\";\n\nconst filePath = process.argv[2];\nif (!filePath) {\n console.error(\"Usage: t2-demo-parser <path-to-rec-file>\");\n process.exit(1);\n}\n\nconst resolvedPath = path.resolve(filePath);\nconsole.log(`Parsing: ${resolvedPath}`);\nconsole.log(`File size: ${fs.statSync(resolvedPath).size} bytes`);\nconsole.log();\n\nconst buffer = fs.readFileSync(resolvedPath);\n\n// ================================================================\n// Full parse with automatic discovery\n// ================================================================\nconst parser = new DemoParser(buffer);\nconst demo = await parser.parseFullDemo();\n\nconst registry = parser.getRegistry();\nfunction isPacketBlock(\n b: DemoBlock\n): b is DemoBlock & { parsed: PacketData } {\n return (\n b.type === BlockTypePacket &&\n b.parsed !== undefined &&\n \"dnetHeader\" in b.parsed\n );\n}\n\nconst packetBlocks = demo.blocks.filter((b) => b.type === BlockTypePacket);\nconst parsedPackets = demo.blocks.filter(isPacketBlock);\n\n// ================================================================\n// DataBlock summary from initial block parsing\n// ================================================================\n{\n const shapeBlocks = [...demo.initialBlock.dataBlocks.values()].filter(\n (db) => db.data.shapeName\n );\n if (shapeBlocks.length > 0) {\n console.log(`DataBlocks with shapeName (${shapeBlocks.length} total):`);\n for (const db of shapeBlocks.slice(0, 20)) {\n console.log(\n ` objId=${db.objectId} classId=${db.classId} [${db.className}]: ${db.data.shapeName}`\n );\n }\n if (shapeBlocks.length > 20) {\n console.log(` ... and ${shapeBlocks.length - 20} more`);\n }\n }\n\n const dbByClass = new Map<string, number>();\n for (const [_objId, db] of demo.initialBlock.dataBlocks) {\n dbByClass.set(db.className, (dbByClass.get(db.className) || 0) + 1);\n }\n if (dbByClass.size > 0) {\n console.log(\"DataBlock types parsed:\");\n for (const [name, count] of [...dbByClass.entries()].sort(\n (a, b) => b[1] - a[1]\n )) {\n console.log(` ${name}: ${count}`);\n }\n }\n}\n\n// ================================================================\n// Output results\n// ================================================================\nconsole.log();\nconsole.log(\"=== Summary ===\");\nconsole.log(\n `Protocol version: 0x${demo.header.protocolVersion.toString(16)}`\n);\nconsole.log(\n `Demo length: ${demo.header.demoLengthMs}ms (${formatTime(demo.header.demoLengthMs)})`\n);\n\nconst moveBlocks = demo.blocks.filter((b) => b.type === BlockTypeMove);\nconst infoBlocks = demo.blocks.filter((b) => b.type === BlockTypeInfo);\nconst sendPacketBlocks = demo.blocks.filter(\n (b) => b.type === BlockTypeSendPacket\n);\n\n// Analyze ghost failures and unbound operations\nconst ghostFailuresByClass = new Map<string, number>();\nconst unboundGhostOps = new Map<number, { creates: number; updates: number }>();\nfor (const block of parsedPackets) {\n const pkt = block.parsed;\n for (const ghost of pkt.ghosts) {\n if (ghost.classId !== undefined && ghost.parsedData === undefined && ghost.type !== \"delete\") {\n const binding = registry.getGhostParser(ghost.classId);\n if (binding) {\n ghostFailuresByClass.set(binding.name, (ghostFailuresByClass.get(binding.name) || 0) + 1);\n } else {\n const entry = unboundGhostOps.get(ghost.classId) || { creates: 0, updates: 0 };\n if (ghost.type === \"create\") entry.creates++;\n else entry.updates++;\n unboundGhostOps.set(ghost.classId, entry);\n }\n }\n }\n}\nif (ghostFailuresByClass.size > 0) {\n console.log(\"\\nGhost failures by type:\");\n for (const [name, count] of [...ghostFailuresByClass.entries()].sort((a, b) => b[1] - a[1])) {\n console.log(` ${name}: ${count}`);\n }\n}\nif (unboundGhostOps.size > 0) {\n console.log(`\\nUnbound ghost classIds (${unboundGhostOps.size} total):`);\n for (const [classId, ops] of [...unboundGhostOps.entries()].sort(\n (a, b) => (b[1].creates + b[1].updates) - (a[1].creates + a[1].updates)\n ).slice(0, 10)) {\n console.log(` classId ${classId}: ${ops.creates} creates, ${ops.updates} updates`);\n }\n}\n\nconsole.log();\nconsole.log(`Total blocks: ${demo.blocks.length}`);\nconsole.log(` Packet blocks (type 0): ${packetBlocks.length}`);\nconsole.log(` SendPacket blocks (type 1): ${sendPacketBlocks.length}`);\nconsole.log(` Move blocks (type 2): ${moveBlocks.length}`);\nconsole.log(` Info blocks (type 3): ${infoBlocks.length}`);\n\n// Sequence numbers\nif (parsedPackets.length > 0) {\n const seqNums = parsedPackets.map(\n (b) => b.parsed.dnetHeader.seqNumber\n );\n let seqGaps = 0;\n for (let i = 1; i < seqNums.length; i++) {\n const expected = (seqNums[i - 1] + 1) & 0x1ff;\n if (seqNums[i] !== expected) seqGaps++;\n }\n console.log();\n console.log(\n `Sequence numbers: ${seqNums[0]} → ${seqNums[seqNums.length - 1]}, ` +\n `${seqGaps} gaps out of ${seqNums.length - 1} transitions`\n );\n}\n\n// Collect classId stats\nconst eventClassIds = new Map<number, number>();\nconst ghostClassIds = new Map<number, number>();\nfor (const block of parsedPackets) {\n const pkt = block.parsed;\n for (const evt of pkt.events) {\n eventClassIds.set(evt.classId, (eventClassIds.get(evt.classId) || 0) + 1);\n }\n for (const ghost of pkt.ghosts) {\n if (ghost.classId !== undefined) {\n ghostClassIds.set(\n ghost.classId,\n (ghostClassIds.get(ghost.classId) || 0) + 1\n );\n }\n }\n}\n\n// Show event bindings\nif (eventClassIds.size > 0) {\n console.log();\n console.log(`Event classIds (${eventClassIds.size} unique):`);\n for (const [id, count] of [...eventClassIds.entries()]\n .sort((a, b) => b[1] - a[1])\n .slice(0, 15)) {\n const binding = registry.getEventParser(id);\n console.log(\n ` ${id}: ${count} occurrences${binding ? ` → ${binding.name}` : \"\"}`\n );\n }\n}\n\n// Show ghost bindings\nif (ghostClassIds.size > 0) {\n console.log();\n console.log(`Ghost classIds (${ghostClassIds.size} unique):`);\n for (const [id, count] of [...ghostClassIds.entries()]\n .sort((a, b) => b[1] - a[1])\n .slice(0, 15)) {\n const binding = registry.getGhostParser(id);\n console.log(\n ` ${id}: ${count} occurrences${binding ? ` → ${binding.name}` : \"\"}`\n );\n }\n}\n\n// Ghost tracker state\nconst ghostTracker = parser.getGhostTracker();\nconst allGhosts = ghostTracker.getAllGhosts();\nif (allGhosts.size > 0) {\n console.log();\n console.log(`Active ghosts (${allGhosts.size} total):`);\n const classCounts = new Map<string, number>();\n for (const [_idx, ghost] of allGhosts) {\n const name = ghost.className || `unknown(${ghost.classId})`;\n classCounts.set(name, (classCounts.get(name) || 0) + 1);\n }\n for (const [name, count] of [...classCounts.entries()].sort(\n (a, b) => b[1] - a[1]\n )) {\n console.log(` ${name}: ${count}`);\n }\n}\n\n// Player position trajectory\nconst positionSamples: {\n blockIdx: number;\n pos: { x: number; y: number; z: number };\n}[] = [];\nfor (const block of parsedPackets) {\n const pkt = block.parsed;\n if (pkt.gameState.compressionPoint) {\n positionSamples.push({\n blockIdx: block.index,\n pos: pkt.gameState.compressionPoint,\n });\n }\n const cod = pkt.gameState.controlObjectData;\n const codPos = cod?.position;\n if (codPos && typeof codPos === \"object\" && \"x\" in codPos) {\n positionSamples.push({\n blockIdx: block.index,\n pos: codPos as { x: number; y: number; z: number },\n });\n }\n}\n\nif (positionSamples.length > 0) {\n console.log();\n console.log(`Player position samples: ${positionSamples.length}`);\n const step = Math.max(1, Math.floor(positionSamples.length / 8));\n for (let i = 0; i < positionSamples.length; i += step) {\n const s = positionSamples[i];\n console.log(\n ` block[${s.blockIdx}]: (${s.pos.x.toFixed(1)}, ${s.pos.y.toFixed(1)}, ${s.pos.z.toFixed(1)})`\n );\n }\n}\n\n// Ghost updates with position data\nlet ghostsWithPosition = 0;\nfor (const block of parsedPackets) {\n const pkt = block.parsed;\n for (const ghost of pkt.ghosts) {\n if (ghost.parsedData?.position) ghostsWithPosition++;\n }\n}\nif (ghostsWithPosition > 0) {\n console.log();\n console.log(`Ghost updates with position data: ${ghostsWithPosition}`);\n}\n\n// First 5 packets detail\nif (parsedPackets.length > 0) {\n console.log();\n console.log(\"First 5 packets:\");\n for (const block of parsedPackets.slice(0, 5)) {\n const pkt = block.parsed;\n console.log(\n ` [${block.index}] seq=${pkt.dnetHeader.seqNumber} events=${pkt.events.length} ghosts=${pkt.ghosts.length}`\n );\n if (pkt.gameState.controlObjectGhostIndex !== undefined) {\n console.log(\n ` controlObject=ghost#${pkt.gameState.controlObjectGhostIndex}` +\n (pkt.gameState.controlObjectData ? \" (parsed)\" : \" (unparsed)\")\n );\n }\n if (pkt.gameState.compressionPoint) {\n const cp = pkt.gameState.compressionPoint;\n console.log(\n ` pos=(${cp.x.toFixed(1)}, ${cp.y.toFixed(1)}, ${cp.z.toFixed(1)})`\n );\n }\n if (pkt.gameState.cameraFov !== undefined) {\n console.log(` cameraFov=${pkt.gameState.cameraFov}`);\n }\n for (const evt of pkt.events.slice(0, 3)) {\n const p = evt.parsedData;\n console.log(\n ` event: classId=${evt.classId}${p ? ` → ${String(p.type) || \"?\"}` : \" (unparsed)\"}`\n );\n }\n for (const ghost of pkt.ghosts.slice(0, 3)) {\n const name = ghost.classId !== undefined\n ? registry.getGhostParser(ghost.classId)?.name || `?`\n : \"\";\n console.log(\n ` ghost: idx=${ghost.index} ${ghost.type}${name ? ` [${name}]` : \"\"}${ghost.parsedData ? \" (parsed)\" : \"\"}`\n );\n }\n }\n}\n\n// Parsed moves\nconst parsedMoves = moveBlocks.filter((b) => b.parsed && \"yaw\" in b.parsed);\nconsole.log();\nconsole.log(`Parsed moves: ${parsedMoves.length}`);\n\n// ================================================================\n// Build timeline\n// ================================================================\nconsole.log();\nconsole.log(\"=== Timeline ===\");\nconst timeline = buildTimeline(demo, registry);\nconst stats = getTimelineStats(timeline);\n\nconsole.log(\n `Duration: ${formatTime(stats.durationMs)}, ` +\n `tick interval: ${stats.tickIntervalMs.toFixed(1)}ms, ` +\n `${stats.packetCount} packets`\n);\nconsole.log(`Control object keyframes: ${stats.controlObjectKeyframes}`);\nconsole.log(\n `Ghost instances: ${stats.totalGhostInstances} (${stats.ghostsWithPosition} with position data)`\n);\n\n// Ghost instances by class (top types)\nif (stats.ghostInstancesByClass.size > 0) {\n console.log();\n console.log(\"Ghost instances by class:\");\n for (const [name, count] of [...stats.ghostInstancesByClass.entries()]\n .sort((a, b) => b[1] - a[1])\n .slice(0, 15)) {\n const kfCount = stats.ghostKeyframesByClass.get(name) || 0;\n console.log(` ${name}: ${count} instances, ${kfCount} keyframes`);\n }\n}\n\n// Events summary\nif (stats.eventsByType.size > 0) {\n console.log();\n console.log(`Events: ${stats.totalEvents} total`);\n for (const [type, count] of [...stats.eventsByType.entries()]\n .sort((a, b) => b[1] - a[1])\n .slice(0, 10)) {\n console.log(` ${type}: ${count}`);\n }\n}\n\n// Sample player positions from timeline\nif (timeline.controlObject.length > 0) {\n console.log();\n console.log(\"Player trajectory (from timeline):\");\n const step = Math.max(1, Math.floor(timeline.controlObject.length / 6));\n for (let i = 0; i < timeline.controlObject.length; i += step) {\n const co = timeline.controlObject[i];\n if (co.position) {\n console.log(\n ` ${formatTime(co.time)}: (${co.position.x.toFixed(1)}, ${co.position.y.toFixed(1)}, ${co.position.z.toFixed(1)})`\n );\n }\n }\n}\n\n// Sample Player ghost trajectories\nconst playerInstances = timeline.ghostInstances.filter(\n (inst) => inst.className === \"Player\" && inst.keyframes.some((kf) => kf.position)\n);\nif (playerInstances.length > 0) {\n console.log();\n console.log(`Player ghost instances: ${playerInstances.length}`);\n for (const inst of playerInstances.slice(0, 3)) {\n const posKfs = inst.keyframes.filter((kf) => kf.position);\n const first = posKfs[0];\n const last = posKfs[posKfs.length - 1];\n console.log(\n ` ghost#${inst.ghostIndex}: ${posKfs.length} position keyframes, ` +\n `${formatTime(inst.spawnTime)}–${inst.despawnTime !== undefined ? formatTime(inst.despawnTime) : \"active\"}`\n );\n if (first?.position && last?.position) {\n console.log(\n ` first: (${first.position.x.toFixed(1)}, ${first.position.y.toFixed(1)}, ${first.position.z.toFixed(1)}) at ${formatTime(first.time)}`\n );\n console.log(\n ` last: (${last.position.x.toFixed(1)}, ${last.position.y.toFixed(1)}, ${last.position.z.toFixed(1)}) at ${formatTime(last.time)}`\n );\n }\n }\n}\n\n// ================================================================\n// Experiment metrics export (optional via env)\n// ================================================================\n{\n const metricsPath = process.env.EXPERIMENT_METRICS_PATH;\n if (metricsPath) {\n const metrics = {\n timestampIso: new Date().toISOString(),\n inputFile: resolvedPath,\n initialPhase2: {\n valid: demo.initialBlock.phase2Valid ?? false,\n trailingBits: demo.initialBlock.phase2TrailingBits ?? null,\n error: demo.initialBlock.phase2Error ?? null,\n events: demo.initialBlock.initialEvents.length,\n ghosts: demo.initialBlock.initialGhosts.length,\n ghostingSequence: demo.initialBlock.ghostingSequence,\n controlObjectGhostIndex: demo.initialBlock.controlObjectGhostIndex,\n missionName: demo.initialBlock.missionName,\n missionCRC: demo.initialBlock.missionCRC,\n },\n counts: {\n blocksTotal: demo.blocks.length,\n packetBlocks: packetBlocks.length,\n parsedPackets: parsedPackets.length,\n moveBlocks: moveBlocks.length,\n infoBlocks: infoBlocks.length,\n eventClassIdsUnique: eventClassIds.size,\n ghostClassIdsUnique: ghostClassIds.size,\n unboundGhostClassIds: unboundGhostOps.size,\n },\n topUnboundGhostClassIds: [...unboundGhostOps.entries()]\n .sort(\n (a, b) =>\n b[1].creates +\n b[1].updates -\n (a[1].creates + a[1].updates)\n )\n .slice(0, 20)\n .map(([classId, ops]) => ({\n classId,\n creates: ops.creates,\n updates: ops.updates,\n })),\n };\n\n const resolvedMetricsPath = path.resolve(metricsPath);\n fs.writeFileSync(resolvedMetricsPath, JSON.stringify(metrics, null, 2));\n console.log();\n console.log(`Experiment metrics written: ${resolvedMetricsPath}`);\n }\n}\n\n// ================================================================\n// JSON export (optional --json flag)\n// ================================================================\nif (process.argv.includes(\"--json\")) {\n const exportData = exportTimeline(timeline);\n const outputPath = resolvedPath.replace(/\\.rec$/i, \".timeline.json\");\n fs.writeFileSync(outputPath, JSON.stringify(exportData));\n console.log();\n console.log(`Timeline exported to: ${outputPath}`);\n console.log(\n ` ${exportData.controlObject.length} control keyframes, ` +\n `${exportData.ghosts.length} ghost tracks, ` +\n `${exportData.events.length} events`\n );\n}\n\nfunction formatTime(ms: number): string {\n const totalSeconds = Math.floor(ms / 1000);\n const minutes = Math.floor(totalSeconds / 60);\n const seconds = totalSeconds % 60;\n return `${minutes}m${seconds.toString().padStart(2, \"0\")}s`;\n}\n"]}
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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","sourcesContent":["#!/usr/bin/env node\nimport * as fs from \"fs\";\nimport * as path from \"path\";\nimport { DemoParser } from \"./DemoParser.js\";\nimport {\n BlockTypePacket,\n BlockTypeSendPacket,\n BlockTypeMove,\n BlockTypeInfo,\n} from \"./types.js\";\nimport type { DemoBlock, PacketData } from \"./types.js\";\nimport {\n buildTimeline,\n getTimelineStats,\n exportTimeline,\n} from \"./Timeline.js\";\n\nconst filePath = process.argv[2];\nif (!filePath) {\n console.error(\"Usage: t2-demo-parser <path-to-rec-file>\");\n process.exit(1);\n}\n\nconst resolvedPath = path.resolve(filePath);\nconsole.log(`Parsing: ${resolvedPath}`);\nconsole.log(`File size: ${fs.statSync(resolvedPath).size} bytes`);\nconsole.log();\n\nconst buffer = fs.readFileSync(resolvedPath);\n\n// ================================================================\n// Full parse with automatic discovery\n// ================================================================\nconst parser = new DemoParser(buffer);\nconst demo = await parser.parseFullDemo();\n\nconst registry = parser.getRegistry();\nfunction isPacketBlock(\n b: DemoBlock\n): b is DemoBlock & { parsed: PacketData } {\n return (\n b.type === BlockTypePacket &&\n b.parsed !== undefined &&\n \"dnetHeader\" in b.parsed\n );\n}\n\nconst packetBlocks = demo.blocks.filter((b) => b.type === BlockTypePacket);\nconst parsedPackets = demo.blocks.filter(isPacketBlock);\n\n// ================================================================\n// DataBlock summary from initial block parsing\n// ================================================================\n{\n const shapeBlocks = [...demo.initialBlock.dataBlocks.values()].filter(\n (db) => db.data.shapeName\n );\n if (shapeBlocks.length > 0) {\n console.log(`DataBlocks with shapeName (${shapeBlocks.length} total):`);\n for (const db of shapeBlocks.slice(0, 20)) {\n console.log(\n ` objId=${db.objectId} classId=${db.classId} [${db.className}]: ${db.data.shapeName}`\n );\n }\n if (shapeBlocks.length > 20) {\n console.log(` ... and ${shapeBlocks.length - 20} more`);\n }\n }\n\n const dbByClass = new Map<string, number>();\n for (const [_objId, db] of demo.initialBlock.dataBlocks) {\n dbByClass.set(db.className, (dbByClass.get(db.className) || 0) + 1);\n }\n if (dbByClass.size > 0) {\n console.log(\"DataBlock types parsed:\");\n for (const [name, count] of [...dbByClass.entries()].sort(\n (a, b) => b[1] - a[1]\n )) {\n console.log(` ${name}: ${count}`);\n }\n }\n}\n\n// ================================================================\n// Output results\n// ================================================================\nconsole.log();\nconsole.log(\"=== Summary ===\");\nconsole.log(\n `Protocol version: 0x${demo.header.protocolVersion.toString(16)}`\n);\nconsole.log(\n `Demo length: ${demo.header.demoLengthMs}ms (${formatTime(demo.header.demoLengthMs)})`\n);\nfor (const warning of demo.initialBlock.warnings) {\n console.log(`WARNING (initial block): ${warning}`);\n}\n\nconst moveBlocks = demo.blocks.filter((b) => b.type === BlockTypeMove);\nconst infoBlocks = demo.blocks.filter((b) => b.type === BlockTypeInfo);\nconst sendPacketBlocks = demo.blocks.filter(\n (b) => b.type === BlockTypeSendPacket\n);\n\n// Analyze ghost failures and unbound operations\nconst ghostFailuresByClass = new Map<string, number>();\nconst unboundGhostOps = new Map<number, { creates: number; updates: number }>();\nfor (const block of parsedPackets) {\n const pkt = block.parsed;\n for (const ghost of pkt.ghosts) {\n if (ghost.classId !== undefined && ghost.parsedData === undefined && ghost.type !== \"delete\") {\n const binding = registry.getGhostParser(ghost.classId);\n if (binding) {\n ghostFailuresByClass.set(binding.name, (ghostFailuresByClass.get(binding.name) || 0) + 1);\n } else {\n const entry = unboundGhostOps.get(ghost.classId) || { creates: 0, updates: 0 };\n if (ghost.type === \"create\") entry.creates++;\n else entry.updates++;\n unboundGhostOps.set(ghost.classId, entry);\n }\n }\n }\n}\nif (ghostFailuresByClass.size > 0) {\n console.log(\"\\nGhost failures by type:\");\n for (const [name, count] of [...ghostFailuresByClass.entries()].sort((a, b) => b[1] - a[1])) {\n console.log(` ${name}: ${count}`);\n }\n}\nif (unboundGhostOps.size > 0) {\n console.log(`\\nUnbound ghost classIds (${unboundGhostOps.size} total):`);\n for (const [classId, ops] of [...unboundGhostOps.entries()].sort(\n (a, b) => (b[1].creates + b[1].updates) - (a[1].creates + a[1].updates)\n ).slice(0, 10)) {\n console.log(` classId ${classId}: ${ops.creates} creates, ${ops.updates} updates`);\n }\n}\n\nconsole.log();\nconsole.log(`Total blocks: ${demo.blocks.length}`);\nconsole.log(` Packet blocks (type 0): ${packetBlocks.length}`);\nconsole.log(` SendPacket blocks (type 1): ${sendPacketBlocks.length}`);\nconsole.log(` Move blocks (type 2): ${moveBlocks.length}`);\nconsole.log(` Info blocks (type 3): ${infoBlocks.length}`);\n\n// Sequence numbers\nif (parsedPackets.length > 0) {\n const seqNums = parsedPackets.map(\n (b) => b.parsed.dnetHeader.seqNumber\n );\n let seqGaps = 0;\n for (let i = 1; i < seqNums.length; i++) {\n const expected = (seqNums[i - 1] + 1) & 0x1ff;\n if (seqNums[i] !== expected) seqGaps++;\n }\n console.log();\n console.log(\n `Sequence numbers: ${seqNums[0]} → ${seqNums[seqNums.length - 1]}, ` +\n `${seqGaps} gaps out of ${seqNums.length - 1} transitions`\n );\n}\n\n// Collect classId stats\nconst eventClassIds = new Map<number, number>();\nconst ghostClassIds = new Map<number, number>();\nfor (const block of parsedPackets) {\n const pkt = block.parsed;\n for (const evt of pkt.events) {\n eventClassIds.set(evt.classId, (eventClassIds.get(evt.classId) || 0) + 1);\n }\n for (const ghost of pkt.ghosts) {\n if (ghost.classId !== undefined) {\n ghostClassIds.set(\n ghost.classId,\n (ghostClassIds.get(ghost.classId) || 0) + 1\n );\n }\n }\n}\n\n// Show event bindings\nif (eventClassIds.size > 0) {\n console.log();\n console.log(`Event classIds (${eventClassIds.size} unique):`);\n for (const [id, count] of [...eventClassIds.entries()]\n .sort((a, b) => b[1] - a[1])\n .slice(0, 15)) {\n const binding = registry.getEventParser(id);\n console.log(\n ` ${id}: ${count} occurrences${binding ? ` → ${binding.name}` : \"\"}`\n );\n }\n}\n\n// Show ghost bindings\nif (ghostClassIds.size > 0) {\n console.log();\n console.log(`Ghost classIds (${ghostClassIds.size} unique):`);\n for (const [id, count] of [...ghostClassIds.entries()]\n .sort((a, b) => b[1] - a[1])\n .slice(0, 15)) {\n const binding = registry.getGhostParser(id);\n console.log(\n ` ${id}: ${count} occurrences${binding ? ` → ${binding.name}` : \"\"}`\n );\n }\n}\n\n// Ghost tracker state\nconst ghostTracker = parser.getGhostTracker();\nconst allGhosts = ghostTracker.getAllGhosts();\nif (allGhosts.size > 0) {\n console.log();\n console.log(`Active ghosts (${allGhosts.size} total):`);\n const classCounts = new Map<string, number>();\n for (const [_idx, ghost] of allGhosts) {\n const name = ghost.className || `unknown(${ghost.classId})`;\n classCounts.set(name, (classCounts.get(name) || 0) + 1);\n }\n for (const [name, count] of [...classCounts.entries()].sort(\n (a, b) => b[1] - a[1]\n )) {\n console.log(` ${name}: ${count}`);\n }\n}\n\n// Player position trajectory\nconst positionSamples: {\n blockIdx: number;\n pos: { x: number; y: number; z: number };\n}[] = [];\nfor (const block of parsedPackets) {\n const pkt = block.parsed;\n if (pkt.gameState.compressionPoint) {\n positionSamples.push({\n blockIdx: block.index,\n pos: pkt.gameState.compressionPoint,\n });\n }\n const cod = pkt.gameState.controlObjectData;\n const codPos = cod?.position;\n if (codPos && typeof codPos === \"object\" && \"x\" in codPos) {\n positionSamples.push({\n blockIdx: block.index,\n pos: codPos as { x: number; y: number; z: number },\n });\n }\n}\n\nif (positionSamples.length > 0) {\n console.log();\n console.log(`Player position samples: ${positionSamples.length}`);\n const step = Math.max(1, Math.floor(positionSamples.length / 8));\n for (let i = 0; i < positionSamples.length; i += step) {\n const s = positionSamples[i];\n console.log(\n ` block[${s.blockIdx}]: (${s.pos.x.toFixed(1)}, ${s.pos.y.toFixed(1)}, ${s.pos.z.toFixed(1)})`\n );\n }\n}\n\n// Ghost updates with position data\nlet ghostsWithPosition = 0;\nfor (const block of parsedPackets) {\n const pkt = block.parsed;\n for (const ghost of pkt.ghosts) {\n if (ghost.parsedData?.position) ghostsWithPosition++;\n }\n}\nif (ghostsWithPosition > 0) {\n console.log();\n console.log(`Ghost updates with position data: ${ghostsWithPosition}`);\n}\n\n// First 5 packets detail\nif (parsedPackets.length > 0) {\n console.log();\n console.log(\"First 5 packets:\");\n for (const block of parsedPackets.slice(0, 5)) {\n const pkt = block.parsed;\n console.log(\n ` [${block.index}] seq=${pkt.dnetHeader.seqNumber} events=${pkt.events.length} ghosts=${pkt.ghosts.length}`\n );\n if (pkt.gameState.controlObjectGhostIndex !== undefined) {\n console.log(\n ` controlObject=ghost#${pkt.gameState.controlObjectGhostIndex}` +\n (pkt.gameState.controlObjectData ? \" (parsed)\" : \" (unparsed)\")\n );\n }\n if (pkt.gameState.compressionPoint) {\n const cp = pkt.gameState.compressionPoint;\n console.log(\n ` pos=(${cp.x.toFixed(1)}, ${cp.y.toFixed(1)}, ${cp.z.toFixed(1)})`\n );\n }\n if (pkt.gameState.cameraFov !== undefined) {\n console.log(` cameraFov=${pkt.gameState.cameraFov}`);\n }\n for (const evt of pkt.events.slice(0, 3)) {\n const p = evt.parsedData;\n console.log(\n ` event: classId=${evt.classId}${p ? ` → ${String(p.type) || \"?\"}` : \" (unparsed)\"}`\n );\n }\n for (const ghost of pkt.ghosts.slice(0, 3)) {\n const name = ghost.classId !== undefined\n ? registry.getGhostParser(ghost.classId)?.name || `?`\n : \"\";\n console.log(\n ` ghost: idx=${ghost.index} ${ghost.type}${name ? ` [${name}]` : \"\"}${ghost.parsedData ? \" (parsed)\" : \"\"}`\n );\n }\n }\n}\n\n// Parsed moves\nconst parsedMoves = moveBlocks.filter((b) => b.parsed && \"yaw\" in b.parsed);\nconsole.log();\nconsole.log(`Parsed moves: ${parsedMoves.length}`);\n\n// ================================================================\n// Build timeline\n// ================================================================\nconsole.log();\nconsole.log(\"=== Timeline ===\");\nconst timeline = buildTimeline(demo, registry);\nconst stats = getTimelineStats(timeline);\n\nconsole.log(\n `Duration: ${formatTime(stats.durationMs)}, ` +\n `tick interval: ${stats.tickIntervalMs.toFixed(1)}ms, ` +\n `${stats.packetCount} packets`\n);\nconsole.log(`Control object keyframes: ${stats.controlObjectKeyframes}`);\nconsole.log(\n `Ghost instances: ${stats.totalGhostInstances} (${stats.ghostsWithPosition} with position data)`\n);\n\n// Ghost instances by class (top types)\nif (stats.ghostInstancesByClass.size > 0) {\n console.log();\n console.log(\"Ghost instances by class:\");\n for (const [name, count] of [...stats.ghostInstancesByClass.entries()]\n .sort((a, b) => b[1] - a[1])\n .slice(0, 15)) {\n const kfCount = stats.ghostKeyframesByClass.get(name) || 0;\n console.log(` ${name}: ${count} instances, ${kfCount} keyframes`);\n }\n}\n\n// Events summary\nif (stats.eventsByType.size > 0) {\n console.log();\n console.log(`Events: ${stats.totalEvents} total`);\n for (const [type, count] of [...stats.eventsByType.entries()]\n .sort((a, b) => b[1] - a[1])\n .slice(0, 10)) {\n console.log(` ${type}: ${count}`);\n }\n}\n\n// Sample player positions from timeline\nif (timeline.controlObject.length > 0) {\n console.log();\n console.log(\"Player trajectory (from timeline):\");\n const step = Math.max(1, Math.floor(timeline.controlObject.length / 6));\n for (let i = 0; i < timeline.controlObject.length; i += step) {\n const co = timeline.controlObject[i];\n if (co.position) {\n console.log(\n ` ${formatTime(co.time)}: (${co.position.x.toFixed(1)}, ${co.position.y.toFixed(1)}, ${co.position.z.toFixed(1)})`\n );\n }\n }\n}\n\n// Sample Player ghost trajectories\nconst playerInstances = timeline.ghostInstances.filter(\n (inst) => inst.className === \"Player\" && inst.keyframes.some((kf) => kf.position)\n);\nif (playerInstances.length > 0) {\n console.log();\n console.log(`Player ghost instances: ${playerInstances.length}`);\n for (const inst of playerInstances.slice(0, 3)) {\n const posKfs = inst.keyframes.filter((kf) => kf.position);\n const first = posKfs[0];\n const last = posKfs[posKfs.length - 1];\n console.log(\n ` ghost#${inst.ghostIndex}: ${posKfs.length} position keyframes, ` +\n `${formatTime(inst.spawnTime)}–${inst.despawnTime !== undefined ? formatTime(inst.despawnTime) : \"active\"}`\n );\n if (first?.position && last?.position) {\n console.log(\n ` first: (${first.position.x.toFixed(1)}, ${first.position.y.toFixed(1)}, ${first.position.z.toFixed(1)}) at ${formatTime(first.time)}`\n );\n console.log(\n ` last: (${last.position.x.toFixed(1)}, ${last.position.y.toFixed(1)}, ${last.position.z.toFixed(1)}) at ${formatTime(last.time)}`\n );\n }\n }\n}\n\n// ================================================================\n// Experiment metrics export (optional via env)\n// ================================================================\n{\n const metricsPath = process.env.EXPERIMENT_METRICS_PATH;\n if (metricsPath) {\n const metrics = {\n timestampIso: new Date().toISOString(),\n inputFile: resolvedPath,\n initialPhase2: {\n valid: demo.initialBlock.phase2Valid ?? false,\n trailingBits: demo.initialBlock.phase2TrailingBits ?? null,\n error: demo.initialBlock.phase2Error ?? null,\n events: demo.initialBlock.initialEvents.length,\n ghosts: demo.initialBlock.initialGhosts.length,\n ghostingSequence: demo.initialBlock.ghostingSequence,\n controlObjectGhostIndex: demo.initialBlock.controlObjectGhostIndex,\n missionName: demo.initialBlock.missionName,\n missionCRC: demo.initialBlock.missionCRC,\n },\n counts: {\n blocksTotal: demo.blocks.length,\n packetBlocks: packetBlocks.length,\n parsedPackets: parsedPackets.length,\n moveBlocks: moveBlocks.length,\n infoBlocks: infoBlocks.length,\n eventClassIdsUnique: eventClassIds.size,\n ghostClassIdsUnique: ghostClassIds.size,\n unboundGhostClassIds: unboundGhostOps.size,\n },\n topUnboundGhostClassIds: [...unboundGhostOps.entries()]\n .sort(\n (a, b) =>\n b[1].creates +\n b[1].updates -\n (a[1].creates + a[1].updates)\n )\n .slice(0, 20)\n .map(([classId, ops]) => ({\n classId,\n creates: ops.creates,\n updates: ops.updates,\n })),\n };\n\n const resolvedMetricsPath = path.resolve(metricsPath);\n fs.writeFileSync(resolvedMetricsPath, JSON.stringify(metrics, null, 2));\n console.log();\n console.log(`Experiment metrics written: ${resolvedMetricsPath}`);\n }\n}\n\n// ================================================================\n// JSON export (optional --json flag)\n// ================================================================\nif (process.argv.includes(\"--json\")) {\n const exportData = exportTimeline(timeline);\n const outputPath = resolvedPath.replace(/\\.rec$/i, \".timeline.json\");\n fs.writeFileSync(outputPath, JSON.stringify(exportData));\n console.log();\n console.log(`Timeline exported to: ${outputPath}`);\n console.log(\n ` ${exportData.controlObject.length} control keyframes, ` +\n `${exportData.ghosts.length} ghost tracks, ` +\n `${exportData.events.length} events`\n );\n}\n\nfunction formatTime(ms: number): string {\n const totalSeconds = Math.floor(ms / 1000);\n const minutes = Math.floor(totalSeconds / 60);\n const seconds = totalSeconds % 60;\n return `${minutes}m${seconds.toString().padStart(2, \"0\")}s`;\n}\n"]}