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.
- package/README.md +170 -7
- package/dist/BitStream.d.ts +14 -3
- package/dist/BitStream.d.ts.map +1 -1
- package/dist/BitStream.js +28 -6
- package/dist/BitStream.js.map +1 -1
- package/dist/BitWriter.d.ts +29 -0
- package/dist/BitWriter.d.ts.map +1 -0
- package/dist/BitWriter.js +76 -0
- package/dist/BitWriter.js.map +1 -0
- package/dist/ClassRegistry.d.ts +23 -28
- package/dist/ClassRegistry.d.ts.map +1 -1
- package/dist/ClassRegistry.js +75 -84
- package/dist/ClassRegistry.js.map +1 -1
- package/dist/DataBlockParsers.d.ts.map +1 -1
- package/dist/DataBlockParsers.js +60 -85
- package/dist/DataBlockParsers.js.map +1 -1
- package/dist/DemoParser.d.ts +23 -22
- package/dist/DemoParser.d.ts.map +1 -1
- package/dist/DemoParser.js +218 -266
- package/dist/DemoParser.js.map +1 -1
- package/dist/EventParsers.d.ts.map +1 -1
- package/dist/EventParsers.js +87 -58
- package/dist/EventParsers.js.map +1 -1
- package/dist/GhostManager.d.ts +7 -1
- package/dist/GhostManager.d.ts.map +1 -1
- package/dist/GhostManager.js +231 -146
- package/dist/GhostManager.js.map +1 -1
- package/dist/HuffmanProcessor.d.ts +15 -0
- package/dist/HuffmanProcessor.d.ts.map +1 -1
- package/dist/HuffmanProcessor.js +45 -0
- package/dist/HuffmanProcessor.js.map +1 -1
- package/dist/LiveParser.d.ts +20 -1
- package/dist/LiveParser.d.ts.map +1 -1
- package/dist/LiveParser.js +31 -12
- package/dist/LiveParser.js.map +1 -1
- package/dist/PacketParser.d.ts +22 -2
- package/dist/PacketParser.d.ts.map +1 -1
- package/dist/PacketParser.js +111 -48
- package/dist/PacketParser.js.map +1 -1
- package/dist/Timeline.d.ts +5 -2
- package/dist/Timeline.d.ts.map +1 -1
- package/dist/Timeline.js +25 -12
- package/dist/Timeline.js.map +1 -1
- package/dist/cli.js +3 -0
- package/dist/cli.js.map +1 -1
- package/dist/dataBlockDataTypes.d.ts +118 -117
- package/dist/dataBlockDataTypes.d.ts.map +1 -1
- package/dist/dataBlockDataTypes.js.map +1 -1
- package/dist/defaultRegistry.d.ts +9 -0
- package/dist/defaultRegistry.d.ts.map +1 -0
- package/dist/defaultRegistry.js +43 -0
- package/dist/defaultRegistry.js.map +1 -0
- package/dist/eventDataTypes.d.ts +74 -72
- package/dist/eventDataTypes.d.ts.map +1 -1
- package/dist/eventDataTypes.js.map +1 -1
- package/dist/ghostDataTypes.d.ts +125 -131
- package/dist/ghostDataTypes.d.ts.map +1 -1
- package/dist/ghostDataTypes.js.map +1 -1
- package/dist/index.d.ts +6 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +51 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -1
- package/package.json +4 -1
- package/dist/BitStream.test.d.ts +0 -2
- package/dist/BitStream.test.d.ts.map +0 -1
- package/dist/BitStream.test.js +0 -419
- package/dist/BitStream.test.js.map +0 -1
- package/dist/ClassRegistry.test.d.ts +0 -2
- package/dist/ClassRegistry.test.d.ts.map +0 -1
- package/dist/ClassRegistry.test.js +0 -83
- package/dist/ClassRegistry.test.js.map +0 -1
- package/dist/DataBlockParsers.test.d.ts +0 -2
- package/dist/DataBlockParsers.test.d.ts.map +0 -1
- package/dist/DataBlockParsers.test.js +0 -294
- package/dist/DataBlockParsers.test.js.map +0 -1
- package/dist/DemoParser.test.d.ts +0 -2
- package/dist/DemoParser.test.d.ts.map +0 -1
- package/dist/DemoParser.test.js +0 -472
- package/dist/DemoParser.test.js.map +0 -1
- package/dist/GhostStateAccumulator.test.d.ts +0 -2
- package/dist/GhostStateAccumulator.test.d.ts.map +0 -1
- package/dist/GhostStateAccumulator.test.js +0 -201
- package/dist/GhostStateAccumulator.test.js.map +0 -1
- package/dist/GhostTracker.test.d.ts +0 -2
- package/dist/GhostTracker.test.d.ts.map +0 -1
- package/dist/GhostTracker.test.js +0 -64
- package/dist/GhostTracker.test.js.map +0 -1
- package/dist/IncrementalParser.test.d.ts +0 -2
- package/dist/IncrementalParser.test.d.ts.map +0 -1
- package/dist/IncrementalParser.test.js +0 -226
- package/dist/IncrementalParser.test.js.map +0 -1
package/dist/DemoParser.test.js
DELETED
|
@@ -1,472 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import * as fs from "fs";
|
|
3
|
-
import * as path from "path";
|
|
4
|
-
import { DemoParser } from "./DemoParser.js";
|
|
5
|
-
import { BlockTypePacket, BlockTypeMove, BlockTypeInfo, } from "./types.js";
|
|
6
|
-
/**
|
|
7
|
-
* Integration tests that parse real demo files and verify zero failures.
|
|
8
|
-
* These are the primary regression tests — any parser change that causes
|
|
9
|
-
* ghost failures, tracker divergences, or incorrect counts will be caught.
|
|
10
|
-
*/
|
|
11
|
-
const DEMO_DIR = path.resolve(import.meta.dirname, "..", "data");
|
|
12
|
-
/** Demo files with expected parse metrics. */
|
|
13
|
-
const DEMOS = [
|
|
14
|
-
{
|
|
15
|
-
file: "exogen_Harvester.rec",
|
|
16
|
-
minPackets: 28000,
|
|
17
|
-
minGhostCreates: 17000,
|
|
18
|
-
minGhostUpdates: 520000,
|
|
19
|
-
},
|
|
20
|
-
{
|
|
21
|
-
file: "uploads_7_2025-04-26_21-30_Tacocat_CTFGame_MisadventureV2.rec",
|
|
22
|
-
minPackets: 58000,
|
|
23
|
-
minGhostCreates: 23000,
|
|
24
|
-
minGhostUpdates: 1100000,
|
|
25
|
-
},
|
|
26
|
-
{
|
|
27
|
-
file: "uploads_4_6_15_24_Massive_DannoProCaptain.rec",
|
|
28
|
-
minPackets: 30000,
|
|
29
|
-
minGhostCreates: 18000,
|
|
30
|
-
minGhostUpdates: 580000,
|
|
31
|
-
},
|
|
32
|
-
{
|
|
33
|
-
file: "uploads_6_SterIO_2025_LT_Pub_SH.rec",
|
|
34
|
-
minPackets: 58000,
|
|
35
|
-
minGhostCreates: 16000,
|
|
36
|
-
minGhostUpdates: 1200000,
|
|
37
|
-
},
|
|
38
|
-
{
|
|
39
|
-
file: "uploads_6_SterIO_2025_TeamNev_vs_TeamBlake_WoodyMyrk.rec",
|
|
40
|
-
minPackets: 21000,
|
|
41
|
-
minGhostCreates: 11000,
|
|
42
|
-
minGhostUpdates: 380000,
|
|
43
|
-
},
|
|
44
|
-
{
|
|
45
|
-
file: "uploads_8_MapFour3-1.rec",
|
|
46
|
-
minPackets: 34000,
|
|
47
|
-
minGhostCreates: 16000,
|
|
48
|
-
minGhostUpdates: 710000,
|
|
49
|
-
},
|
|
50
|
-
{
|
|
51
|
-
file: "auto-capture_2025-12-03_01-52_DemoBot-Mia_CTFGame_El_FinLT.rec",
|
|
52
|
-
minPackets: 51000,
|
|
53
|
-
minGhostCreates: 5000,
|
|
54
|
-
minGhostUpdates: 450000,
|
|
55
|
-
},
|
|
56
|
-
];
|
|
57
|
-
function demoExists(file) {
|
|
58
|
-
return fs.existsSync(path.join(DEMO_DIR, file));
|
|
59
|
-
}
|
|
60
|
-
async function parseDemoFile(file) {
|
|
61
|
-
const buf = fs.readFileSync(path.join(DEMO_DIR, file));
|
|
62
|
-
const parser = new DemoParser(buf);
|
|
63
|
-
const result = await parser.parseFullDemo();
|
|
64
|
-
return { parser, result, pp: parser.getPacketParser() };
|
|
65
|
-
}
|
|
66
|
-
describe("DemoParser integration", () => {
|
|
67
|
-
for (const demo of DEMOS) {
|
|
68
|
-
const shortName = demo.file
|
|
69
|
-
.replace(/^uploads_\d+_/, "")
|
|
70
|
-
.replace(/\.rec$/, "");
|
|
71
|
-
it(`${shortName}: 0 failures, 0 divergences`, { timeout: 60_000, skip: !demoExists(demo.file) }, async () => {
|
|
72
|
-
const { result, pp } = await parseDemoFile(demo.file);
|
|
73
|
-
// Zero tolerance for failures
|
|
74
|
-
expect(pp.ghostsFailed).toBe(0);
|
|
75
|
-
expect(pp.ghostsTrackerDiverged).toBe(0);
|
|
76
|
-
// Sanity: verify we actually parsed a meaningful amount of data
|
|
77
|
-
expect(pp.packetsParsed).toBeGreaterThanOrEqual(demo.minPackets);
|
|
78
|
-
expect(pp.ghostCreatesParsed).toBeGreaterThanOrEqual(demo.minGhostCreates);
|
|
79
|
-
expect(pp.ghostUpdatesParsed).toBeGreaterThanOrEqual(demo.minGhostUpdates);
|
|
80
|
-
// Header sanity
|
|
81
|
-
expect(result.header.identString).toBe("Tribes2 Recording");
|
|
82
|
-
expect(result.header.protocolVersion).toBe(0x330004);
|
|
83
|
-
// Blocks exist
|
|
84
|
-
expect(result.blocks.length).toBeGreaterThan(0);
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
});
|
|
88
|
-
describe("DemoParser structure", () => {
|
|
89
|
-
const demoFile = "exogen_Harvester.rec";
|
|
90
|
-
const skip = !demoExists(demoFile);
|
|
91
|
-
it("parses header correctly", { skip, timeout: 30_000 }, async () => {
|
|
92
|
-
const { result } = await parseDemoFile(demoFile);
|
|
93
|
-
expect(result.header.identString).toBe("Tribes2 Recording");
|
|
94
|
-
expect(result.header.protocolVersion).toBe(0x330004);
|
|
95
|
-
expect(result.header.demoLengthMs).toBeGreaterThan(0);
|
|
96
|
-
});
|
|
97
|
-
it("parses initial block with DataBlocks", { skip, timeout: 30_000 }, async () => {
|
|
98
|
-
const { result } = await parseDemoFile(demoFile);
|
|
99
|
-
expect(result.initialBlock).toBeDefined();
|
|
100
|
-
expect(result.initialBlock.dataBlocks.size).toBeGreaterThan(0);
|
|
101
|
-
});
|
|
102
|
-
it("parses initial ghosts", { skip, timeout: 30_000 }, async () => {
|
|
103
|
-
const { result } = await parseDemoFile(demoFile);
|
|
104
|
-
expect(result.initialBlock.initialGhosts).toBeDefined();
|
|
105
|
-
expect(result.initialBlock.initialGhosts.length).toBeGreaterThan(0);
|
|
106
|
-
});
|
|
107
|
-
it("exposes registry with correct binding counts", { skip, timeout: 30_000 }, async () => {
|
|
108
|
-
const { parser } = await parseDemoFile(demoFile);
|
|
109
|
-
const reg = parser.getRegistry();
|
|
110
|
-
const ghostBindings = reg.getGhostBindings();
|
|
111
|
-
const eventBindings = reg.getEventBindings();
|
|
112
|
-
// 53 ghost classes, 26 event classes
|
|
113
|
-
expect(ghostBindings.size).toBe(53);
|
|
114
|
-
expect(eventBindings.size).toBe(26);
|
|
115
|
-
});
|
|
116
|
-
it("WheeledVehicle always reads braking/wheels (regression)", { timeout: 60_000, skip: !demoExists("uploads_7_2025-04-26_21-30_Tacocat_CTFGame_MisadventureV2.rec") }, async () => {
|
|
117
|
-
// The Tacocat demo previously had 32 ghost failures from WV misparse
|
|
118
|
-
const { pp } = await parseDemoFile("uploads_7_2025-04-26_21-30_Tacocat_CTFGame_MisadventureV2.rec");
|
|
119
|
-
expect(pp.ghostsFailed).toBe(0);
|
|
120
|
-
expect(pp.ghostsTrackerDiverged).toBe(0);
|
|
121
|
-
});
|
|
122
|
-
});
|
|
123
|
-
describe("DemoParser stepping API", () => {
|
|
124
|
-
const demoFile = "exogen_Harvester.rec";
|
|
125
|
-
const skip = !demoExists(demoFile);
|
|
126
|
-
function loadDemo() {
|
|
127
|
-
const buf = fs.readFileSync(path.join(DEMO_DIR, demoFile));
|
|
128
|
-
return new DemoParser(buf);
|
|
129
|
-
}
|
|
130
|
-
it("getters before load() throw, loaded returns false", { skip }, () => {
|
|
131
|
-
const parser = loadDemo();
|
|
132
|
-
expect(parser.loaded).toBe(false);
|
|
133
|
-
expect(() => parser.header).toThrow("must call load() first");
|
|
134
|
-
expect(() => parser.initialBlock).toThrow("must call load() first");
|
|
135
|
-
expect(() => parser.blockCount).toThrow("must call load() first");
|
|
136
|
-
expect(() => parser.blockCursor).toThrow("must call load() first");
|
|
137
|
-
});
|
|
138
|
-
it("nextBlock() throws before load()", { skip }, () => {
|
|
139
|
-
const parser = loadDemo();
|
|
140
|
-
expect(() => parser.nextBlock()).toThrow("must call load() first");
|
|
141
|
-
});
|
|
142
|
-
it("reset() throws before load()", { skip }, () => {
|
|
143
|
-
const parser = loadDemo();
|
|
144
|
-
expect(() => parser.reset()).toThrow("must call load() first");
|
|
145
|
-
});
|
|
146
|
-
it("load() returns header/initialBlock with valid data", { skip, timeout: 30_000 }, async () => {
|
|
147
|
-
const parser = loadDemo();
|
|
148
|
-
const result = await parser.load();
|
|
149
|
-
expect(result.header.identString).toBe("Tribes2 Recording");
|
|
150
|
-
expect(result.header.protocolVersion).toBe(0x330004);
|
|
151
|
-
expect(result.header.demoLengthMs).toBeGreaterThan(0);
|
|
152
|
-
expect(result.initialBlock.dataBlocks.size).toBeGreaterThan(0);
|
|
153
|
-
expect(result.initialBlock.initialGhosts.length).toBeGreaterThan(0);
|
|
154
|
-
expect(result.initialBlock.missionName.length).toBeGreaterThan(0);
|
|
155
|
-
});
|
|
156
|
-
it("load() is idempotent", { skip, timeout: 30_000 }, async () => {
|
|
157
|
-
const parser = loadDemo();
|
|
158
|
-
const result1 = await parser.load();
|
|
159
|
-
const result2 = await parser.load();
|
|
160
|
-
expect(result2.header).toBe(result1.header);
|
|
161
|
-
expect(result2.initialBlock).toBe(result1.initialBlock);
|
|
162
|
-
});
|
|
163
|
-
it("load() populates getters", { skip, timeout: 30_000 }, async () => {
|
|
164
|
-
const parser = loadDemo();
|
|
165
|
-
await parser.load();
|
|
166
|
-
expect(parser.loaded).toBe(true);
|
|
167
|
-
expect(parser.header.identString).toBe("Tribes2 Recording");
|
|
168
|
-
expect(parser.initialBlock.dataBlocks.size).toBeGreaterThan(0);
|
|
169
|
-
expect(parser.blockCount).toBeGreaterThan(0);
|
|
170
|
-
expect(parser.blockCursor).toBe(0);
|
|
171
|
-
});
|
|
172
|
-
it("nextBlock() returns blocks sequentially and advances cursor", { skip, timeout: 30_000 }, async () => {
|
|
173
|
-
const parser = loadDemo();
|
|
174
|
-
await parser.load();
|
|
175
|
-
const block0 = parser.nextBlock();
|
|
176
|
-
expect(block0).toBeDefined();
|
|
177
|
-
expect(block0.index).toBe(0);
|
|
178
|
-
expect(parser.blockCursor).toBe(1);
|
|
179
|
-
const block1 = parser.nextBlock();
|
|
180
|
-
expect(block1).toBeDefined();
|
|
181
|
-
expect(block1.index).toBe(1);
|
|
182
|
-
expect(parser.blockCursor).toBe(2);
|
|
183
|
-
});
|
|
184
|
-
it("nextBlock() parses blocks with correct types", { skip, timeout: 60_000 }, async () => {
|
|
185
|
-
const parser = loadDemo();
|
|
186
|
-
await parser.load();
|
|
187
|
-
let packets = 0;
|
|
188
|
-
let moves = 0;
|
|
189
|
-
let infos = 0;
|
|
190
|
-
let block;
|
|
191
|
-
while ((block = parser.nextBlock())) {
|
|
192
|
-
if (block.type === BlockTypePacket && block.parsed)
|
|
193
|
-
packets++;
|
|
194
|
-
if (block.type === BlockTypeMove && block.parsed)
|
|
195
|
-
moves++;
|
|
196
|
-
if (block.type === BlockTypeInfo && block.parsed)
|
|
197
|
-
infos++;
|
|
198
|
-
}
|
|
199
|
-
expect(packets).toBeGreaterThan(0);
|
|
200
|
-
expect(moves).toBeGreaterThan(0);
|
|
201
|
-
expect(infos).toBeGreaterThan(0);
|
|
202
|
-
});
|
|
203
|
-
it("nextBlock() returns undefined when exhausted", { skip, timeout: 60_000 }, async () => {
|
|
204
|
-
const parser = loadDemo();
|
|
205
|
-
await parser.load();
|
|
206
|
-
while (parser.nextBlock()) { }
|
|
207
|
-
expect(parser.nextBlock()).toBeUndefined();
|
|
208
|
-
expect(parser.blockCursor).toBe(parser.blockCount);
|
|
209
|
-
});
|
|
210
|
-
it("reset() resets cursor and produces identical stats on replay", { skip, timeout: 120_000 }, async () => {
|
|
211
|
-
const parser = loadDemo();
|
|
212
|
-
await parser.load();
|
|
213
|
-
// First pass
|
|
214
|
-
while (parser.nextBlock()) { }
|
|
215
|
-
const pp1 = parser.getPacketParser();
|
|
216
|
-
const stats1 = {
|
|
217
|
-
packetsParsed: pp1.packetsParsed,
|
|
218
|
-
ghostCreatesParsed: pp1.ghostCreatesParsed,
|
|
219
|
-
ghostUpdatesParsed: pp1.ghostUpdatesParsed,
|
|
220
|
-
ghostDeletes: pp1.ghostDeletes,
|
|
221
|
-
ghostsFailed: pp1.ghostsFailed,
|
|
222
|
-
ghostsTrackerDiverged: pp1.ghostsTrackerDiverged,
|
|
223
|
-
eventsParsed: pp1.eventsParsed,
|
|
224
|
-
controlObjectParsed: pp1.controlObjectParsed,
|
|
225
|
-
};
|
|
226
|
-
// Reset and replay
|
|
227
|
-
parser.reset();
|
|
228
|
-
expect(parser.blockCursor).toBe(0);
|
|
229
|
-
while (parser.nextBlock()) { }
|
|
230
|
-
const pp2 = parser.getPacketParser();
|
|
231
|
-
const stats2 = {
|
|
232
|
-
packetsParsed: pp2.packetsParsed,
|
|
233
|
-
ghostCreatesParsed: pp2.ghostCreatesParsed,
|
|
234
|
-
ghostUpdatesParsed: pp2.ghostUpdatesParsed,
|
|
235
|
-
ghostDeletes: pp2.ghostDeletes,
|
|
236
|
-
ghostsFailed: pp2.ghostsFailed,
|
|
237
|
-
ghostsTrackerDiverged: pp2.ghostsTrackerDiverged,
|
|
238
|
-
eventsParsed: pp2.eventsParsed,
|
|
239
|
-
controlObjectParsed: pp2.controlObjectParsed,
|
|
240
|
-
};
|
|
241
|
-
expect(stats2).toEqual(stats1);
|
|
242
|
-
});
|
|
243
|
-
it("stepping produces same stats as parseFullDemo()", { skip, timeout: 120_000 }, async () => {
|
|
244
|
-
// Parse with parseFullDemo
|
|
245
|
-
const buf = fs.readFileSync(path.join(DEMO_DIR, demoFile));
|
|
246
|
-
const fullParser = new DemoParser(buf);
|
|
247
|
-
await fullParser.parseFullDemo();
|
|
248
|
-
const fullPP = fullParser.getPacketParser();
|
|
249
|
-
const fullStats = {
|
|
250
|
-
packetsParsed: fullPP.packetsParsed,
|
|
251
|
-
ghostCreatesParsed: fullPP.ghostCreatesParsed,
|
|
252
|
-
ghostUpdatesParsed: fullPP.ghostUpdatesParsed,
|
|
253
|
-
ghostDeletes: fullPP.ghostDeletes,
|
|
254
|
-
ghostsFailed: fullPP.ghostsFailed,
|
|
255
|
-
ghostsTrackerDiverged: fullPP.ghostsTrackerDiverged,
|
|
256
|
-
eventsParsed: fullPP.eventsParsed,
|
|
257
|
-
controlObjectParsed: fullPP.controlObjectParsed,
|
|
258
|
-
};
|
|
259
|
-
// Parse with stepping API
|
|
260
|
-
const stepParser = new DemoParser(buf);
|
|
261
|
-
await stepParser.load();
|
|
262
|
-
while (stepParser.nextBlock()) { }
|
|
263
|
-
const stepPP = stepParser.getPacketParser();
|
|
264
|
-
const stepStats = {
|
|
265
|
-
packetsParsed: stepPP.packetsParsed,
|
|
266
|
-
ghostCreatesParsed: stepPP.ghostCreatesParsed,
|
|
267
|
-
ghostUpdatesParsed: stepPP.ghostUpdatesParsed,
|
|
268
|
-
ghostDeletes: stepPP.ghostDeletes,
|
|
269
|
-
ghostsFailed: stepPP.ghostsFailed,
|
|
270
|
-
ghostsTrackerDiverged: stepPP.ghostsTrackerDiverged,
|
|
271
|
-
eventsParsed: stepPP.eventsParsed,
|
|
272
|
-
controlObjectParsed: stepPP.controlObjectParsed,
|
|
273
|
-
};
|
|
274
|
-
expect(stepStats).toEqual(fullStats);
|
|
275
|
-
});
|
|
276
|
-
it("parseFullDemo() backward compat: sets loaded state", { skip, timeout: 30_000 }, async () => {
|
|
277
|
-
const parser = loadDemo();
|
|
278
|
-
await parser.parseFullDemo();
|
|
279
|
-
expect(parser.loaded).toBe(true);
|
|
280
|
-
expect(parser.blockCursor).toBe(parser.blockCount);
|
|
281
|
-
expect(parser.nextBlock()).toBeUndefined();
|
|
282
|
-
});
|
|
283
|
-
it("processBlocks() fast-forwards correct count", { skip, timeout: 60_000 }, async () => {
|
|
284
|
-
const parser = loadDemo();
|
|
285
|
-
await parser.load();
|
|
286
|
-
const processed = parser.processBlocks(100);
|
|
287
|
-
expect(processed).toBe(100);
|
|
288
|
-
expect(parser.blockCursor).toBe(100);
|
|
289
|
-
// Drain the rest
|
|
290
|
-
while (parser.nextBlock()) { }
|
|
291
|
-
// processBlocks returns 0 when exhausted
|
|
292
|
-
expect(parser.processBlocks(10)).toBe(0);
|
|
293
|
-
});
|
|
294
|
-
it("processBlocks() produces same stats as nextBlock() drain", { skip, timeout: 120_000 }, async () => {
|
|
295
|
-
const buf = fs.readFileSync(path.join(DEMO_DIR, demoFile));
|
|
296
|
-
// Drain with nextBlock
|
|
297
|
-
const parser1 = new DemoParser(buf);
|
|
298
|
-
await parser1.load();
|
|
299
|
-
while (parser1.nextBlock()) { }
|
|
300
|
-
const pp1 = parser1.getPacketParser();
|
|
301
|
-
const stats1 = {
|
|
302
|
-
packetsParsed: pp1.packetsParsed,
|
|
303
|
-
ghostCreatesParsed: pp1.ghostCreatesParsed,
|
|
304
|
-
ghostUpdatesParsed: pp1.ghostUpdatesParsed,
|
|
305
|
-
ghostsFailed: pp1.ghostsFailed,
|
|
306
|
-
};
|
|
307
|
-
// Drain with processBlocks
|
|
308
|
-
const parser2 = new DemoParser(buf);
|
|
309
|
-
await parser2.load();
|
|
310
|
-
parser2.processBlocks(Infinity);
|
|
311
|
-
const pp2 = parser2.getPacketParser();
|
|
312
|
-
const stats2 = {
|
|
313
|
-
packetsParsed: pp2.packetsParsed,
|
|
314
|
-
ghostCreatesParsed: pp2.ghostCreatesParsed,
|
|
315
|
-
ghostUpdatesParsed: pp2.ghostUpdatesParsed,
|
|
316
|
-
ghostsFailed: pp2.ghostsFailed,
|
|
317
|
-
};
|
|
318
|
-
expect(stats2).toEqual(stats1);
|
|
319
|
-
});
|
|
320
|
-
});
|
|
321
|
-
describe("DemoParser data exposure", () => {
|
|
322
|
-
const demoFile = "exogen_Harvester.rec";
|
|
323
|
-
const demoWithEvents = "uploads_6_SterIO_2025_LT_Pub_SH.rec";
|
|
324
|
-
const skipDemo = !demoExists(demoFile);
|
|
325
|
-
const skipEvents = !demoExists(demoWithEvents);
|
|
326
|
-
it("initial block exposes sensorGroupColors", { skip: skipDemo, timeout: 30_000 }, async () => {
|
|
327
|
-
const { result } = await parseDemoFile(demoFile);
|
|
328
|
-
const colors = result.initialBlock.sensorGroupColors;
|
|
329
|
-
expect(colors).toBeDefined();
|
|
330
|
-
expect(Array.isArray(colors)).toBe(true);
|
|
331
|
-
// demo022 has 5 sensor group color entries (2 teams + default)
|
|
332
|
-
expect(colors.length).toBeGreaterThan(0);
|
|
333
|
-
// Each entry has proper structure
|
|
334
|
-
for (const c of colors) {
|
|
335
|
-
expect(c).toHaveProperty("group");
|
|
336
|
-
expect(c).toHaveProperty("targetGroup");
|
|
337
|
-
expect(c.r).toBeGreaterThanOrEqual(0);
|
|
338
|
-
expect(c.r).toBeLessThanOrEqual(255);
|
|
339
|
-
expect(c.g).toBeGreaterThanOrEqual(0);
|
|
340
|
-
expect(c.g).toBeLessThanOrEqual(255);
|
|
341
|
-
expect(c.b).toBeGreaterThanOrEqual(0);
|
|
342
|
-
expect(c.b).toBeLessThanOrEqual(255);
|
|
343
|
-
expect(c.a).toBeGreaterThanOrEqual(0);
|
|
344
|
-
expect(c.a).toBeLessThanOrEqual(255);
|
|
345
|
-
}
|
|
346
|
-
// Friendly self-color is green
|
|
347
|
-
const greenEntries = colors.filter((c) => c.r === 0 && c.g === 255 && c.b === 0);
|
|
348
|
-
expect(greenEntries.length).toBeGreaterThan(0);
|
|
349
|
-
});
|
|
350
|
-
it("initial block exposes pathManager", { skip: skipDemo, timeout: 30_000 }, async () => {
|
|
351
|
-
const { result } = await parseDemoFile(demoFile);
|
|
352
|
-
const pm = result.initialBlock.pathManager;
|
|
353
|
-
expect(pm).toBeDefined();
|
|
354
|
-
expect(Array.isArray(pm)).toBe(true);
|
|
355
|
-
// pathManager may be empty for some maps, but the field must exist
|
|
356
|
-
for (const entry of pm) {
|
|
357
|
-
expect(entry).toHaveProperty("entryId");
|
|
358
|
-
expect(entry).toHaveProperty("records");
|
|
359
|
-
expect(Array.isArray(entry.records)).toBe(true);
|
|
360
|
-
}
|
|
361
|
-
});
|
|
362
|
-
it("packets expose targetVisibility when present", { skip: skipDemo, timeout: 60_000 }, async () => {
|
|
363
|
-
const { result } = await parseDemoFile(demoFile);
|
|
364
|
-
let found = 0;
|
|
365
|
-
for (const block of result.blocks) {
|
|
366
|
-
if (block.type !== BlockTypePacket || !block.parsed)
|
|
367
|
-
continue;
|
|
368
|
-
const pkt = block.parsed;
|
|
369
|
-
if (pkt.gameState.targetVisibility) {
|
|
370
|
-
found++;
|
|
371
|
-
for (const tv of pkt.gameState.targetVisibility) {
|
|
372
|
-
expect(typeof tv.index).toBe("number");
|
|
373
|
-
expect(tv.index).toBeGreaterThanOrEqual(0);
|
|
374
|
-
expect(tv.index).toBeLessThan(16);
|
|
375
|
-
expect(typeof tv.mask).toBe("number");
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
// targetVisibility is sent in most game packets
|
|
380
|
-
expect(found).toBeGreaterThan(0);
|
|
381
|
-
});
|
|
382
|
-
it("SensorGroupColorEvent parsed in packet stream", { skip: skipEvents, timeout: 60_000 }, async () => {
|
|
383
|
-
const { result } = await parseDemoFile(demoWithEvents);
|
|
384
|
-
let sgColorCount = 0;
|
|
385
|
-
let setSgCount = 0;
|
|
386
|
-
for (const block of result.blocks) {
|
|
387
|
-
if (block.type !== BlockTypePacket || !block.parsed)
|
|
388
|
-
continue;
|
|
389
|
-
const pkt = block.parsed;
|
|
390
|
-
for (const evt of pkt.events) {
|
|
391
|
-
if (evt.parsedData?.type === "SensorGroupColorEvent") {
|
|
392
|
-
sgColorCount++;
|
|
393
|
-
expect(evt.parsedData.sensorGroup).toBeDefined();
|
|
394
|
-
expect(evt.parsedData.colors).toBeDefined();
|
|
395
|
-
}
|
|
396
|
-
if (evt.parsedData?.type === "SetSensorGroupEvent") {
|
|
397
|
-
setSgCount++;
|
|
398
|
-
expect(typeof evt.parsedData.sensorGroup).toBe("number");
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
expect(sgColorCount).toBeGreaterThan(0);
|
|
403
|
-
expect(setSgCount).toBeGreaterThan(0);
|
|
404
|
-
});
|
|
405
|
-
it("GravityEvent returns float value", { skip: skipEvents, timeout: 60_000 }, async () => {
|
|
406
|
-
// Check that GravityEvent gravity field, if present, is a float not an integer bit pattern
|
|
407
|
-
const { result } = await parseDemoFile(demoWithEvents);
|
|
408
|
-
for (const block of result.blocks) {
|
|
409
|
-
if (block.type !== BlockTypePacket || !block.parsed)
|
|
410
|
-
continue;
|
|
411
|
-
const pkt = block.parsed;
|
|
412
|
-
for (const evt of pkt.events) {
|
|
413
|
-
if (evt.parsedData?.type === "GravityEvent") {
|
|
414
|
-
const g = evt.parsedData.gravity;
|
|
415
|
-
// Gravity is a physical constant, typically -20 to -33 in Tribes 2
|
|
416
|
-
// A U32 misread would give ~3.2 billion
|
|
417
|
-
expect(Math.abs(g)).toBeLessThan(1000);
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
});
|
|
422
|
-
});
|
|
423
|
-
describe("CloakMask field names (binary-verified)", () => {
|
|
424
|
-
const demoFile = "exogen_Harvester.rec";
|
|
425
|
-
const skip = !demoExists(demoFile);
|
|
426
|
-
it("CloakMask uses correct field names and types", { skip, timeout: 60_000 }, async () => {
|
|
427
|
-
const { result } = await parseDemoFile(demoFile);
|
|
428
|
-
// Collect all ghost data with CloakMask fields across the full demo.
|
|
429
|
-
let withCloaked = 0;
|
|
430
|
-
let withFadeVal = 0;
|
|
431
|
-
let withFading = 0;
|
|
432
|
-
let withFadeTime = 0;
|
|
433
|
-
function checkGhosts(ghosts) {
|
|
434
|
-
for (const ghost of ghosts) {
|
|
435
|
-
const d = ghost.parsedData;
|
|
436
|
-
if (!d)
|
|
437
|
-
continue;
|
|
438
|
-
// Old field names must be absent.
|
|
439
|
-
expect(d.stateAEnabled).toBeUndefined();
|
|
440
|
-
expect(d.stateB).toBeUndefined();
|
|
441
|
-
expect(d.hasInvulnerability).toBeUndefined();
|
|
442
|
-
expect(d.invulnerabilityVisual).toBeUndefined();
|
|
443
|
-
expect(d.invulnerabilityTicks).toBeUndefined();
|
|
444
|
-
expect(d.binaryCloak).toBeUndefined();
|
|
445
|
-
if (typeof d.cloaked === "boolean")
|
|
446
|
-
withCloaked++;
|
|
447
|
-
if (typeof d.fadeVal === "boolean")
|
|
448
|
-
withFadeVal++;
|
|
449
|
-
if (typeof d.fading === "boolean")
|
|
450
|
-
withFading++;
|
|
451
|
-
if (typeof d.fadeTime === "number") {
|
|
452
|
-
withFadeTime++;
|
|
453
|
-
// fadeTime is F32 (seconds), not U32. A misread U32 would give
|
|
454
|
-
// values in the millions; valid fade durations are 0–few seconds.
|
|
455
|
-
expect(d.fadeTime).toBeGreaterThanOrEqual(0);
|
|
456
|
-
expect(d.fadeTime).toBeLessThan(60);
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
checkGhosts(result.initialBlock.initialGhosts);
|
|
461
|
-
for (const block of result.blocks) {
|
|
462
|
-
if (block.type !== BlockTypePacket || !block.parsed)
|
|
463
|
-
continue;
|
|
464
|
-
checkGhosts(block.parsed.ghosts);
|
|
465
|
-
}
|
|
466
|
-
// Sanity: CloakMask data should appear in a real demo.
|
|
467
|
-
expect(withCloaked).toBeGreaterThan(0);
|
|
468
|
-
expect(withFadeVal).toBeGreaterThan(0);
|
|
469
|
-
expect(withFading).toBeGreaterThanOrEqual(0); // fading may not occur in every demo
|
|
470
|
-
});
|
|
471
|
-
});
|
|
472
|
-
//# sourceMappingURL=DemoParser.test.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"DemoParser.test.js","sourceRoot":"","sources":["../src/DemoParser.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { describe, it, expect } from \"vitest\";\nimport * as fs from \"fs\";\nimport * as path from \"path\";\nimport { DemoParser } from \"./DemoParser.js\";\nimport {\n BlockTypePacket,\n BlockTypeMove,\n BlockTypeInfo,\n} from \"./types.js\";\nimport type { PacketData } from \"./types.js\";\n\n/**\n * Integration tests that parse real demo files and verify zero failures.\n * These are the primary regression tests — any parser change that causes\n * ghost failures, tracker divergences, or incorrect counts will be caught.\n */\n\nconst DEMO_DIR = path.resolve(import.meta.dirname, \"..\", \"data\");\n\n/** Demo files with expected parse metrics. */\nconst DEMOS = [\n {\n file: \"exogen_Harvester.rec\",\n minPackets: 28000,\n minGhostCreates: 17000,\n minGhostUpdates: 520000,\n },\n {\n file: \"uploads_7_2025-04-26_21-30_Tacocat_CTFGame_MisadventureV2.rec\",\n minPackets: 58000,\n minGhostCreates: 23000,\n minGhostUpdates: 1100000,\n },\n {\n file: \"uploads_4_6_15_24_Massive_DannoProCaptain.rec\",\n minPackets: 30000,\n minGhostCreates: 18000,\n minGhostUpdates: 580000,\n },\n {\n file: \"uploads_6_SterIO_2025_LT_Pub_SH.rec\",\n minPackets: 58000,\n minGhostCreates: 16000,\n minGhostUpdates: 1200000,\n },\n {\n file: \"uploads_6_SterIO_2025_TeamNev_vs_TeamBlake_WoodyMyrk.rec\",\n minPackets: 21000,\n minGhostCreates: 11000,\n minGhostUpdates: 380000,\n },\n {\n file: \"uploads_8_MapFour3-1.rec\",\n minPackets: 34000,\n minGhostCreates: 16000,\n minGhostUpdates: 710000,\n },\n {\n file: \"auto-capture_2025-12-03_01-52_DemoBot-Mia_CTFGame_El_FinLT.rec\",\n minPackets: 51000,\n minGhostCreates: 5000,\n minGhostUpdates: 450000,\n },\n];\n\nfunction demoExists(file: string): boolean {\n return fs.existsSync(path.join(DEMO_DIR, file));\n}\n\nasync function parseDemoFile(file: string) {\n const buf = fs.readFileSync(path.join(DEMO_DIR, file));\n const parser = new DemoParser(buf);\n const result = await parser.parseFullDemo();\n return { parser, result, pp: parser.getPacketParser() };\n}\n\ndescribe(\"DemoParser integration\", () => {\n for (const demo of DEMOS) {\n const shortName = demo.file\n .replace(/^uploads_\\d+_/, \"\")\n .replace(/\\.rec$/, \"\");\n\n it(`${shortName}: 0 failures, 0 divergences`, { timeout: 60_000, skip: !demoExists(demo.file) }, async () => {\n const { result, pp } = await parseDemoFile(demo.file);\n\n // Zero tolerance for failures\n expect(pp.ghostsFailed).toBe(0);\n expect(pp.ghostsTrackerDiverged).toBe(0);\n\n // Sanity: verify we actually parsed a meaningful amount of data\n expect(pp.packetsParsed).toBeGreaterThanOrEqual(demo.minPackets);\n expect(pp.ghostCreatesParsed).toBeGreaterThanOrEqual(\n demo.minGhostCreates\n );\n expect(pp.ghostUpdatesParsed).toBeGreaterThanOrEqual(\n demo.minGhostUpdates\n );\n\n // Header sanity\n expect(result.header.identString).toBe(\"Tribes2 Recording\");\n expect(result.header.protocolVersion).toBe(0x330004);\n\n // Blocks exist\n expect(result.blocks.length).toBeGreaterThan(0);\n });\n }\n});\n\ndescribe(\"DemoParser structure\", () => {\n const demoFile = \"exogen_Harvester.rec\";\n const skip = !demoExists(demoFile);\n\n it(\"parses header correctly\", { skip, timeout: 30_000 }, async () => {\n const { result } = await parseDemoFile(demoFile);\n\n expect(result.header.identString).toBe(\"Tribes2 Recording\");\n expect(result.header.protocolVersion).toBe(0x330004);\n expect(result.header.demoLengthMs).toBeGreaterThan(0);\n });\n\n it(\"parses initial block with DataBlocks\", { skip, timeout: 30_000 }, async () => {\n const { result } = await parseDemoFile(demoFile);\n\n expect(result.initialBlock).toBeDefined();\n expect(result.initialBlock!.dataBlocks.size).toBeGreaterThan(0);\n });\n\n it(\"parses initial ghosts\", { skip, timeout: 30_000 }, async () => {\n const { result } = await parseDemoFile(demoFile);\n\n expect(result.initialBlock!.initialGhosts).toBeDefined();\n expect(result.initialBlock!.initialGhosts!.length).toBeGreaterThan(0);\n });\n\n it(\"exposes registry with correct binding counts\", { skip, timeout: 30_000 }, async () => {\n const { parser } = await parseDemoFile(demoFile);\n\n const reg = parser.getRegistry();\n const ghostBindings = reg.getGhostBindings();\n const eventBindings = reg.getEventBindings();\n\n // 53 ghost classes, 26 event classes\n expect(ghostBindings.size).toBe(53);\n expect(eventBindings.size).toBe(26);\n });\n\n it(\"WheeledVehicle always reads braking/wheels (regression)\", { timeout: 60_000, skip: !demoExists(\"uploads_7_2025-04-26_21-30_Tacocat_CTFGame_MisadventureV2.rec\") }, async () => {\n // The Tacocat demo previously had 32 ghost failures from WV misparse\n const { pp } = await parseDemoFile(\n \"uploads_7_2025-04-26_21-30_Tacocat_CTFGame_MisadventureV2.rec\"\n );\n expect(pp.ghostsFailed).toBe(0);\n expect(pp.ghostsTrackerDiverged).toBe(0);\n });\n});\n\ndescribe(\"DemoParser stepping API\", () => {\n const demoFile = \"exogen_Harvester.rec\";\n const skip = !demoExists(demoFile);\n\n function loadDemo() {\n const buf = fs.readFileSync(path.join(DEMO_DIR, demoFile));\n return new DemoParser(buf);\n }\n\n it(\"getters before load() throw, loaded returns false\", { skip }, () => {\n const parser = loadDemo();\n expect(parser.loaded).toBe(false);\n expect(() => parser.header).toThrow(\"must call load() first\");\n expect(() => parser.initialBlock).toThrow(\"must call load() first\");\n expect(() => parser.blockCount).toThrow(\"must call load() first\");\n expect(() => parser.blockCursor).toThrow(\"must call load() first\");\n });\n\n it(\"nextBlock() throws before load()\", { skip }, () => {\n const parser = loadDemo();\n expect(() => parser.nextBlock()).toThrow(\"must call load() first\");\n });\n\n it(\"reset() throws before load()\", { skip }, () => {\n const parser = loadDemo();\n expect(() => parser.reset()).toThrow(\"must call load() first\");\n });\n\n it(\"load() returns header/initialBlock with valid data\", { skip, timeout: 30_000 }, async () => {\n const parser = loadDemo();\n const result = await parser.load();\n\n expect(result.header.identString).toBe(\"Tribes2 Recording\");\n expect(result.header.protocolVersion).toBe(0x330004);\n expect(result.header.demoLengthMs).toBeGreaterThan(0);\n expect(result.initialBlock.dataBlocks.size).toBeGreaterThan(0);\n expect(result.initialBlock.initialGhosts.length).toBeGreaterThan(0);\n expect(result.initialBlock.missionName.length).toBeGreaterThan(0);\n });\n\n it(\"load() is idempotent\", { skip, timeout: 30_000 }, async () => {\n const parser = loadDemo();\n const result1 = await parser.load();\n const result2 = await parser.load();\n\n expect(result2.header).toBe(result1.header);\n expect(result2.initialBlock).toBe(result1.initialBlock);\n });\n\n it(\"load() populates getters\", { skip, timeout: 30_000 }, async () => {\n const parser = loadDemo();\n await parser.load();\n\n expect(parser.loaded).toBe(true);\n expect(parser.header.identString).toBe(\"Tribes2 Recording\");\n expect(parser.initialBlock.dataBlocks.size).toBeGreaterThan(0);\n expect(parser.blockCount).toBeGreaterThan(0);\n expect(parser.blockCursor).toBe(0);\n });\n\n it(\"nextBlock() returns blocks sequentially and advances cursor\", { skip, timeout: 30_000 }, async () => {\n const parser = loadDemo();\n await parser.load();\n\n const block0 = parser.nextBlock();\n expect(block0).toBeDefined();\n expect(block0!.index).toBe(0);\n expect(parser.blockCursor).toBe(1);\n\n const block1 = parser.nextBlock();\n expect(block1).toBeDefined();\n expect(block1!.index).toBe(1);\n expect(parser.blockCursor).toBe(2);\n });\n\n it(\"nextBlock() parses blocks with correct types\", { skip, timeout: 60_000 }, async () => {\n const parser = loadDemo();\n await parser.load();\n\n let packets = 0;\n let moves = 0;\n let infos = 0;\n let block: ReturnType<typeof parser.nextBlock>;\n while ((block = parser.nextBlock())) {\n if (block.type === BlockTypePacket && block.parsed) packets++;\n if (block.type === BlockTypeMove && block.parsed) moves++;\n if (block.type === BlockTypeInfo && block.parsed) infos++;\n }\n\n expect(packets).toBeGreaterThan(0);\n expect(moves).toBeGreaterThan(0);\n expect(infos).toBeGreaterThan(0);\n });\n\n it(\"nextBlock() returns undefined when exhausted\", { skip, timeout: 60_000 }, async () => {\n const parser = loadDemo();\n await parser.load();\n\n while (parser.nextBlock()) {}\n\n expect(parser.nextBlock()).toBeUndefined();\n expect(parser.blockCursor).toBe(parser.blockCount);\n });\n\n it(\"reset() resets cursor and produces identical stats on replay\", { skip, timeout: 120_000 }, async () => {\n const parser = loadDemo();\n await parser.load();\n\n // First pass\n while (parser.nextBlock()) {}\n const pp1 = parser.getPacketParser();\n const stats1 = {\n packetsParsed: pp1.packetsParsed,\n ghostCreatesParsed: pp1.ghostCreatesParsed,\n ghostUpdatesParsed: pp1.ghostUpdatesParsed,\n ghostDeletes: pp1.ghostDeletes,\n ghostsFailed: pp1.ghostsFailed,\n ghostsTrackerDiverged: pp1.ghostsTrackerDiverged,\n eventsParsed: pp1.eventsParsed,\n controlObjectParsed: pp1.controlObjectParsed,\n };\n\n // Reset and replay\n parser.reset();\n expect(parser.blockCursor).toBe(0);\n\n while (parser.nextBlock()) {}\n const pp2 = parser.getPacketParser();\n const stats2 = {\n packetsParsed: pp2.packetsParsed,\n ghostCreatesParsed: pp2.ghostCreatesParsed,\n ghostUpdatesParsed: pp2.ghostUpdatesParsed,\n ghostDeletes: pp2.ghostDeletes,\n ghostsFailed: pp2.ghostsFailed,\n ghostsTrackerDiverged: pp2.ghostsTrackerDiverged,\n eventsParsed: pp2.eventsParsed,\n controlObjectParsed: pp2.controlObjectParsed,\n };\n\n expect(stats2).toEqual(stats1);\n });\n\n it(\"stepping produces same stats as parseFullDemo()\", { skip, timeout: 120_000 }, async () => {\n // Parse with parseFullDemo\n const buf = fs.readFileSync(path.join(DEMO_DIR, demoFile));\n const fullParser = new DemoParser(buf);\n await fullParser.parseFullDemo();\n const fullPP = fullParser.getPacketParser();\n const fullStats = {\n packetsParsed: fullPP.packetsParsed,\n ghostCreatesParsed: fullPP.ghostCreatesParsed,\n ghostUpdatesParsed: fullPP.ghostUpdatesParsed,\n ghostDeletes: fullPP.ghostDeletes,\n ghostsFailed: fullPP.ghostsFailed,\n ghostsTrackerDiverged: fullPP.ghostsTrackerDiverged,\n eventsParsed: fullPP.eventsParsed,\n controlObjectParsed: fullPP.controlObjectParsed,\n };\n\n // Parse with stepping API\n const stepParser = new DemoParser(buf);\n await stepParser.load();\n while (stepParser.nextBlock()) {}\n const stepPP = stepParser.getPacketParser();\n const stepStats = {\n packetsParsed: stepPP.packetsParsed,\n ghostCreatesParsed: stepPP.ghostCreatesParsed,\n ghostUpdatesParsed: stepPP.ghostUpdatesParsed,\n ghostDeletes: stepPP.ghostDeletes,\n ghostsFailed: stepPP.ghostsFailed,\n ghostsTrackerDiverged: stepPP.ghostsTrackerDiverged,\n eventsParsed: stepPP.eventsParsed,\n controlObjectParsed: stepPP.controlObjectParsed,\n };\n\n expect(stepStats).toEqual(fullStats);\n });\n\n it(\"parseFullDemo() backward compat: sets loaded state\", { skip, timeout: 30_000 }, async () => {\n const parser = loadDemo();\n await parser.parseFullDemo();\n\n expect(parser.loaded).toBe(true);\n expect(parser.blockCursor).toBe(parser.blockCount);\n expect(parser.nextBlock()).toBeUndefined();\n });\n\n it(\"processBlocks() fast-forwards correct count\", { skip, timeout: 60_000 }, async () => {\n const parser = loadDemo();\n await parser.load();\n\n const processed = parser.processBlocks(100);\n expect(processed).toBe(100);\n expect(parser.blockCursor).toBe(100);\n\n // Drain the rest\n while (parser.nextBlock()) {}\n\n // processBlocks returns 0 when exhausted\n expect(parser.processBlocks(10)).toBe(0);\n });\n\n it(\"processBlocks() produces same stats as nextBlock() drain\", { skip, timeout: 120_000 }, async () => {\n const buf = fs.readFileSync(path.join(DEMO_DIR, demoFile));\n\n // Drain with nextBlock\n const parser1 = new DemoParser(buf);\n await parser1.load();\n while (parser1.nextBlock()) {}\n const pp1 = parser1.getPacketParser();\n const stats1 = {\n packetsParsed: pp1.packetsParsed,\n ghostCreatesParsed: pp1.ghostCreatesParsed,\n ghostUpdatesParsed: pp1.ghostUpdatesParsed,\n ghostsFailed: pp1.ghostsFailed,\n };\n\n // Drain with processBlocks\n const parser2 = new DemoParser(buf);\n await parser2.load();\n parser2.processBlocks(Infinity);\n const pp2 = parser2.getPacketParser();\n const stats2 = {\n packetsParsed: pp2.packetsParsed,\n ghostCreatesParsed: pp2.ghostCreatesParsed,\n ghostUpdatesParsed: pp2.ghostUpdatesParsed,\n ghostsFailed: pp2.ghostsFailed,\n };\n\n expect(stats2).toEqual(stats1);\n });\n});\n\ndescribe(\"DemoParser data exposure\", () => {\n const demoFile = \"exogen_Harvester.rec\";\n const demoWithEvents = \"uploads_6_SterIO_2025_LT_Pub_SH.rec\";\n const skipDemo = !demoExists(demoFile);\n const skipEvents = !demoExists(demoWithEvents);\n\n it(\"initial block exposes sensorGroupColors\", { skip: skipDemo, timeout: 30_000 }, async () => {\n const { result } = await parseDemoFile(demoFile);\n const colors = result.initialBlock.sensorGroupColors;\n\n expect(colors).toBeDefined();\n expect(Array.isArray(colors)).toBe(true);\n // demo022 has 5 sensor group color entries (2 teams + default)\n expect(colors.length).toBeGreaterThan(0);\n\n // Each entry has proper structure\n for (const c of colors) {\n expect(c).toHaveProperty(\"group\");\n expect(c).toHaveProperty(\"targetGroup\");\n expect(c.r).toBeGreaterThanOrEqual(0);\n expect(c.r).toBeLessThanOrEqual(255);\n expect(c.g).toBeGreaterThanOrEqual(0);\n expect(c.g).toBeLessThanOrEqual(255);\n expect(c.b).toBeGreaterThanOrEqual(0);\n expect(c.b).toBeLessThanOrEqual(255);\n expect(c.a).toBeGreaterThanOrEqual(0);\n expect(c.a).toBeLessThanOrEqual(255);\n }\n\n // Friendly self-color is green\n const greenEntries = colors.filter(\n (c) => c.r === 0 && c.g === 255 && c.b === 0\n );\n expect(greenEntries.length).toBeGreaterThan(0);\n });\n\n it(\"initial block exposes pathManager\", { skip: skipDemo, timeout: 30_000 }, async () => {\n const { result } = await parseDemoFile(demoFile);\n const pm = result.initialBlock.pathManager;\n\n expect(pm).toBeDefined();\n expect(Array.isArray(pm)).toBe(true);\n // pathManager may be empty for some maps, but the field must exist\n for (const entry of pm) {\n expect(entry).toHaveProperty(\"entryId\");\n expect(entry).toHaveProperty(\"records\");\n expect(Array.isArray(entry.records)).toBe(true);\n }\n });\n\n it(\"packets expose targetVisibility when present\", { skip: skipDemo, timeout: 60_000 }, async () => {\n const { result } = await parseDemoFile(demoFile);\n let found = 0;\n for (const block of result.blocks) {\n if (block.type !== BlockTypePacket || !block.parsed) continue;\n const pkt = block.parsed as PacketData;\n if (pkt.gameState.targetVisibility) {\n found++;\n for (const tv of pkt.gameState.targetVisibility) {\n expect(typeof tv.index).toBe(\"number\");\n expect(tv.index).toBeGreaterThanOrEqual(0);\n expect(tv.index).toBeLessThan(16);\n expect(typeof tv.mask).toBe(\"number\");\n }\n }\n }\n // targetVisibility is sent in most game packets\n expect(found).toBeGreaterThan(0);\n });\n\n it(\"SensorGroupColorEvent parsed in packet stream\", { skip: skipEvents, timeout: 60_000 }, async () => {\n const { result } = await parseDemoFile(demoWithEvents);\n let sgColorCount = 0;\n let setSgCount = 0;\n for (const block of result.blocks) {\n if (block.type !== BlockTypePacket || !block.parsed) continue;\n const pkt = block.parsed as PacketData;\n for (const evt of pkt.events) {\n if (evt.parsedData?.type === \"SensorGroupColorEvent\") {\n sgColorCount++;\n expect(evt.parsedData.sensorGroup).toBeDefined();\n expect(evt.parsedData.colors).toBeDefined();\n }\n if (evt.parsedData?.type === \"SetSensorGroupEvent\") {\n setSgCount++;\n expect(typeof evt.parsedData.sensorGroup).toBe(\"number\");\n }\n }\n }\n expect(sgColorCount).toBeGreaterThan(0);\n expect(setSgCount).toBeGreaterThan(0);\n });\n\n it(\"GravityEvent returns float value\", { skip: skipEvents, timeout: 60_000 }, async () => {\n // Check that GravityEvent gravity field, if present, is a float not an integer bit pattern\n const { result } = await parseDemoFile(demoWithEvents);\n for (const block of result.blocks) {\n if (block.type !== BlockTypePacket || !block.parsed) continue;\n const pkt = block.parsed as PacketData;\n for (const evt of pkt.events) {\n if (evt.parsedData?.type === \"GravityEvent\") {\n const g = evt.parsedData.gravity as number;\n // Gravity is a physical constant, typically -20 to -33 in Tribes 2\n // A U32 misread would give ~3.2 billion\n expect(Math.abs(g)).toBeLessThan(1000);\n }\n }\n }\n });\n});\n\ndescribe(\"CloakMask field names (binary-verified)\", () => {\n const demoFile = \"exogen_Harvester.rec\";\n const skip = !demoExists(demoFile);\n\n it(\"CloakMask uses correct field names and types\", { skip, timeout: 60_000 }, async () => {\n const { result } = await parseDemoFile(demoFile);\n\n // Collect all ghost data with CloakMask fields across the full demo.\n let withCloaked = 0;\n let withFadeVal = 0;\n let withFading = 0;\n let withFadeTime = 0;\n\n function checkGhosts(ghosts: { parsedData?: Record<string, unknown> }[]) {\n for (const ghost of ghosts) {\n const d = ghost.parsedData;\n if (!d) continue;\n // Old field names must be absent.\n expect(d.stateAEnabled).toBeUndefined();\n expect(d.stateB).toBeUndefined();\n expect(d.hasInvulnerability).toBeUndefined();\n expect(d.invulnerabilityVisual).toBeUndefined();\n expect(d.invulnerabilityTicks).toBeUndefined();\n expect(d.binaryCloak).toBeUndefined();\n\n if (typeof d.cloaked === \"boolean\") withCloaked++;\n if (typeof d.fadeVal === \"boolean\") withFadeVal++;\n if (typeof d.fading === \"boolean\") withFading++;\n if (typeof d.fadeTime === \"number\") {\n withFadeTime++;\n // fadeTime is F32 (seconds), not U32. A misread U32 would give\n // values in the millions; valid fade durations are 0–few seconds.\n expect(d.fadeTime as number).toBeGreaterThanOrEqual(0);\n expect(d.fadeTime as number).toBeLessThan(60);\n }\n }\n }\n\n checkGhosts(result.initialBlock!.initialGhosts);\n for (const block of result.blocks) {\n if (block.type !== BlockTypePacket || !block.parsed) continue;\n checkGhosts((block.parsed as PacketData).ghosts);\n }\n\n // Sanity: CloakMask data should appear in a real demo.\n expect(withCloaked).toBeGreaterThan(0);\n expect(withFadeVal).toBeGreaterThan(0);\n expect(withFading).toBeGreaterThanOrEqual(0); // fading may not occur in every demo\n });\n});\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"GhostStateAccumulator.test.d.ts","sourceRoot":"","sources":["../src/GhostStateAccumulator.test.ts"],"names":[],"mappings":""}
|