@hotlap.ai/telemetry 1.2.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONTRIBUTING.md +3 -3
- package/README.md +4 -2
- package/dist/cli.js +3 -3
- package/dist/{index-t4qz2011.js → index-ndbp3f5t.js} +194 -34
- package/dist/lap-points.d.ts +76 -0
- package/dist/lap-points.d.ts.map +1 -0
- package/dist/live.d.ts +34 -1
- package/dist/live.d.ts.map +1 -1
- package/dist/live.js +1 -1
- package/dist/neutral/lap-points.js +156 -0
- package/dist/telemetry-server.d.ts +100 -1
- package/dist/telemetry-server.d.ts.map +1 -1
- package/dist/telemetry-server.js +462 -126
- package/package.json +9 -2
package/CONTRIBUTING.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# Contributing to @hotlap/telemetry
|
|
1
|
+
# Contributing to @hotlap.ai/telemetry
|
|
2
2
|
|
|
3
|
-
Thank you for your interest in contributing to the HotLap.ai telemetry
|
|
3
|
+
Thank you for your interest in contributing to the HotLap.ai telemetry library!
|
|
4
4
|
|
|
5
5
|
## Getting Started
|
|
6
6
|
|
|
@@ -52,7 +52,7 @@ bun run typecheck
|
|
|
52
52
|
src/
|
|
53
53
|
binary-reader.ts # Low-level binary parsing utilities
|
|
54
54
|
cli.ts # Command-line interface
|
|
55
|
-
constants.ts # iRacing
|
|
55
|
+
constants.ts # iRacing telemetry constants and enums
|
|
56
56
|
ibt.ts # IBT file parser
|
|
57
57
|
index.ts # Main exports
|
|
58
58
|
live.ts # Live telemetry via Windows FFI
|
package/README.md
CHANGED
|
@@ -7,7 +7,9 @@
|
|
|
7
7
|
[](https://www.typescriptlang.org/)
|
|
8
8
|
[](https://bun.sh/)
|
|
9
9
|
|
|
10
|
-
High-performance iRacing telemetry
|
|
10
|
+
High-performance iRacing telemetry library for [Bun](https://bun.sh), built by the [HotLap.ai](https://hotlap.ai) team. Parse IBT telemetry files and stream live data from iRacing via Windows shared memory.
|
|
11
|
+
|
|
12
|
+
> **Note:** This is an unofficial community library, not affiliated with or endorsed by iRacing.com.
|
|
11
13
|
|
|
12
14
|
## Features
|
|
13
15
|
|
|
@@ -193,7 +195,7 @@ ir.pitCommand(pitCommandMode, variable);
|
|
|
193
195
|
| `OilTemp` | float | Oil temperature (C) |
|
|
194
196
|
| `WaterTemp` | float | Water temperature (C) |
|
|
195
197
|
|
|
196
|
-
See iRacing
|
|
198
|
+
See [iRacing documentation](https://www.iracing.com/) for the complete list of variables.
|
|
197
199
|
|
|
198
200
|
## Scripts
|
|
199
201
|
|
package/dist/cli.js
CHANGED
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
} from "./index-6wrtvcny.js";
|
|
6
6
|
import {
|
|
7
7
|
createIRSDK
|
|
8
|
-
} from "./index-
|
|
8
|
+
} from "./index-ndbp3f5t.js";
|
|
9
9
|
import"./index-hxw291vy.js";
|
|
10
10
|
import"./index-c5jf6rjt.js";
|
|
11
11
|
import {
|
|
@@ -36,7 +36,7 @@ var { values, positionals } = parseArgs({
|
|
|
36
36
|
});
|
|
37
37
|
function printHelp() {
|
|
38
38
|
console.log(`
|
|
39
|
-
|
|
39
|
+
HotLap.ai Telemetry CLI v${VERSION}
|
|
40
40
|
|
|
41
41
|
Usage: irsdk [options] [file.ibt]
|
|
42
42
|
|
|
@@ -67,7 +67,7 @@ Examples:
|
|
|
67
67
|
}
|
|
68
68
|
async function main() {
|
|
69
69
|
if (values.version) {
|
|
70
|
-
console.log(`
|
|
70
|
+
console.log(`HotLap.ai Telemetry CLI v${VERSION}`);
|
|
71
71
|
return;
|
|
72
72
|
}
|
|
73
73
|
if (values.help) {
|
|
@@ -104,6 +104,25 @@ function ensureFFILoaded() {
|
|
|
104
104
|
return false;
|
|
105
105
|
}
|
|
106
106
|
}
|
|
107
|
+
var YAML_SPECIAL_FIELDS_REGEX = /((?:DriverSetupName|UserName|TeamName|AbbrevName|Initials): )(.*)/g;
|
|
108
|
+
var YAML_COMMA_VALUE_REGEX = /(\w+: )(,.*)/g;
|
|
109
|
+
function cleanYamlFast(yaml) {
|
|
110
|
+
let cleaned = "";
|
|
111
|
+
for (let i = 0;i < yaml.length; i++) {
|
|
112
|
+
const code = yaml.charCodeAt(i);
|
|
113
|
+
if (code === 129 || code === 141 || code === 143 || code === 144 || code === 157) {
|
|
114
|
+
cleaned += " ";
|
|
115
|
+
} else if (code === 9 || code === 10 || code === 13 || code >= 32 && code <= 126 || code >= 160 && code <= 255) {
|
|
116
|
+
cleaned += yaml[i];
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
cleaned = cleaned.replace(YAML_SPECIAL_FIELDS_REGEX, (_, prefix, value) => {
|
|
120
|
+
const escaped = value.replace(/["\\]/g, "\\$&");
|
|
121
|
+
return `${prefix}"${escaped}"`;
|
|
122
|
+
});
|
|
123
|
+
cleaned = cleaned.replace(YAML_COMMA_VALUE_REGEX, '$1"$2"');
|
|
124
|
+
return cleaned;
|
|
125
|
+
}
|
|
107
126
|
|
|
108
127
|
class IRSDK {
|
|
109
128
|
parseYamlAsync;
|
|
@@ -116,24 +135,74 @@ class IRSDK {
|
|
|
116
135
|
varHeaders = null;
|
|
117
136
|
varHeadersNames = null;
|
|
118
137
|
sessionInfoCache = new Map;
|
|
138
|
+
static sessionRegexCache = new Map;
|
|
119
139
|
broadcastMsgId = null;
|
|
120
140
|
lastSessionInfoUpdate = 0;
|
|
121
141
|
workaroundConnectedState = 0;
|
|
122
142
|
frozenBuffer = null;
|
|
123
143
|
frozenView = null;
|
|
124
144
|
frozenBufOffset = 0;
|
|
145
|
+
frozenBufferCapacity = 0;
|
|
125
146
|
testMode = false;
|
|
126
147
|
isInitialized = false;
|
|
127
148
|
constructor(options = {}) {
|
|
128
149
|
this.parseYamlAsync = options.parseYamlAsync ?? false;
|
|
129
150
|
}
|
|
151
|
+
liveHeaderInt(byteOffset, fallback = 0) {
|
|
152
|
+
if (this.testMode)
|
|
153
|
+
return fallback;
|
|
154
|
+
return this.sharedMemView ? this.sharedMemView.getInt32(byteOffset, true) : fallback;
|
|
155
|
+
}
|
|
156
|
+
get liveStatus() {
|
|
157
|
+
return this.liveHeaderInt(4, this.header?.status ?? 0);
|
|
158
|
+
}
|
|
159
|
+
liveReadBufOffset() {
|
|
160
|
+
const varBufs = this.header.varBuf;
|
|
161
|
+
if (this.testMode || !this.sharedMemView) {
|
|
162
|
+
return getLatestVarBuffer(varBufs).bufOffset;
|
|
163
|
+
}
|
|
164
|
+
let latestTick = -1;
|
|
165
|
+
let latestOffset = varBufs[0].bufOffset;
|
|
166
|
+
let secondTick = -1;
|
|
167
|
+
let secondOffset = varBufs[0].bufOffset;
|
|
168
|
+
for (let i = 0;i < varBufs.length; i++) {
|
|
169
|
+
const tick = this.liveHeaderInt(48 + i * 16);
|
|
170
|
+
const offset = varBufs[i].bufOffset;
|
|
171
|
+
if (tick > latestTick) {
|
|
172
|
+
secondTick = latestTick;
|
|
173
|
+
secondOffset = latestOffset;
|
|
174
|
+
latestTick = tick;
|
|
175
|
+
latestOffset = offset;
|
|
176
|
+
} else if (tick > secondTick) {
|
|
177
|
+
secondTick = tick;
|
|
178
|
+
secondOffset = offset;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return varBufs.length > 1 && secondTick >= 0 ? secondOffset : latestOffset;
|
|
182
|
+
}
|
|
183
|
+
liveLatestBufOffset() {
|
|
184
|
+
const varBufs = this.header.varBuf;
|
|
185
|
+
if (this.testMode || !this.sharedMemView) {
|
|
186
|
+
return varBufs.reduce((a, b) => a.tickCount > b.tickCount ? a : b).bufOffset;
|
|
187
|
+
}
|
|
188
|
+
let latestTick = -1;
|
|
189
|
+
let latestOffset = varBufs[0].bufOffset;
|
|
190
|
+
for (let i = 0;i < varBufs.length; i++) {
|
|
191
|
+
const tick = this.liveHeaderInt(48 + i * 16);
|
|
192
|
+
if (tick > latestTick) {
|
|
193
|
+
latestTick = tick;
|
|
194
|
+
latestOffset = varBufs[i].bufOffset;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return latestOffset;
|
|
198
|
+
}
|
|
130
199
|
get isConnected() {
|
|
131
200
|
if (!this.header)
|
|
132
201
|
return false;
|
|
133
|
-
if (this.
|
|
202
|
+
if (this.liveStatus === StatusField.STATUS_CONNECTED) {
|
|
134
203
|
this.workaroundConnectedState = 0;
|
|
135
204
|
}
|
|
136
|
-
if (this.workaroundConnectedState === 0 && this.
|
|
205
|
+
if (this.workaroundConnectedState === 0 && this.liveStatus !== StatusField.STATUS_CONNECTED) {
|
|
137
206
|
this.workaroundConnectedState = 1;
|
|
138
207
|
}
|
|
139
208
|
if (this.workaroundConnectedState === 1 && this.get("SessionNum") === null) {
|
|
@@ -142,10 +211,10 @@ class IRSDK {
|
|
|
142
211
|
if (this.workaroundConnectedState === 2 && this.get("SessionNum") !== null) {
|
|
143
212
|
this.workaroundConnectedState = 3;
|
|
144
213
|
}
|
|
145
|
-
return (this.dataValidEvent !== null || this.testMode) && (this.
|
|
214
|
+
return (this.dataValidEvent !== null || this.testMode) && (this.liveStatus === StatusField.STATUS_CONNECTED || this.workaroundConnectedState === 3);
|
|
146
215
|
}
|
|
147
216
|
get sessionInfoUpdate() {
|
|
148
|
-
return this.header?.sessionInfoUpdate ?? 0;
|
|
217
|
+
return this.liveHeaderInt(12, this.header?.sessionInfoUpdate ?? 0);
|
|
149
218
|
}
|
|
150
219
|
get varHeaderNames() {
|
|
151
220
|
if (!this.varHeaders)
|
|
@@ -249,27 +318,119 @@ class IRSDK {
|
|
|
249
318
|
if (!varHeader) {
|
|
250
319
|
return this.getSessionInfo(key);
|
|
251
320
|
}
|
|
252
|
-
const
|
|
321
|
+
const isFrozen = this.isFrozen;
|
|
322
|
+
const view = isFrozen ? this.frozenView : this.sharedMemView;
|
|
253
323
|
if (!view)
|
|
254
324
|
return null;
|
|
255
|
-
const
|
|
256
|
-
|
|
257
|
-
|
|
325
|
+
const bufOffset = isFrozen ? this.frozenBufOffset : this.liveReadBufOffset();
|
|
326
|
+
return readVarValue(view, bufOffset + varHeader.offset, varHeader.type, varHeader.count);
|
|
327
|
+
}
|
|
328
|
+
getMultiple(keys) {
|
|
329
|
+
const results = new Map;
|
|
330
|
+
if (!this.header || !this.varHeaders)
|
|
331
|
+
return results;
|
|
332
|
+
const isFrozen = this.isFrozen;
|
|
333
|
+
const view = isFrozen ? this.frozenView : this.sharedMemView;
|
|
334
|
+
if (!view)
|
|
335
|
+
return results;
|
|
336
|
+
const bufOffset = isFrozen ? this.frozenBufOffset : this.liveReadBufOffset();
|
|
337
|
+
for (let i = 0;i < keys.length; i++) {
|
|
338
|
+
const key = keys[i];
|
|
339
|
+
const varHeader = this.varHeaders.get(key);
|
|
340
|
+
if (varHeader) {
|
|
341
|
+
results.set(key, readVarValue(view, bufOffset + varHeader.offset, varHeader.type, varHeader.count));
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
return results;
|
|
345
|
+
}
|
|
346
|
+
getMultipleInto(keys, out) {
|
|
347
|
+
if (!this.header || !this.varHeaders) {
|
|
348
|
+
for (let i = 0;i < keys.length; i++) {
|
|
349
|
+
out[keys[i]] = null;
|
|
350
|
+
}
|
|
351
|
+
return out;
|
|
352
|
+
}
|
|
353
|
+
const isFrozen = this.isFrozen;
|
|
354
|
+
const view = isFrozen ? this.frozenView : this.sharedMemView;
|
|
355
|
+
if (!view)
|
|
356
|
+
return out;
|
|
357
|
+
const bufOffset = isFrozen ? this.frozenBufOffset : this.liveReadBufOffset();
|
|
358
|
+
for (let i = 0;i < keys.length; i++) {
|
|
359
|
+
const key = keys[i];
|
|
360
|
+
const varHeader = this.varHeaders.get(key);
|
|
361
|
+
if (varHeader) {
|
|
362
|
+
out[key] = readVarValue(view, bufOffset + varHeader.offset, varHeader.type, varHeader.count);
|
|
363
|
+
} else {
|
|
364
|
+
out[key] = null;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
return out;
|
|
368
|
+
}
|
|
369
|
+
createReader(keys) {
|
|
370
|
+
if (!this.header || !this.varHeaders) {
|
|
371
|
+
return {
|
|
372
|
+
read: () => keys.map(() => null),
|
|
373
|
+
keys
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
const varInfos = new Array(keys.length);
|
|
377
|
+
for (let i = 0;i < keys.length; i++) {
|
|
378
|
+
const varHeader = this.varHeaders.get(keys[i]);
|
|
379
|
+
if (varHeader) {
|
|
380
|
+
varInfos[i] = { offset: varHeader.offset, type: varHeader.type, count: varHeader.count };
|
|
381
|
+
} else {
|
|
382
|
+
varInfos[i] = null;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
const self = this;
|
|
386
|
+
const results = new Array(keys.length);
|
|
387
|
+
return {
|
|
388
|
+
keys,
|
|
389
|
+
read() {
|
|
390
|
+
if (!self.header) {
|
|
391
|
+
for (let i = 0;i < keys.length; i++)
|
|
392
|
+
results[i] = null;
|
|
393
|
+
return results;
|
|
394
|
+
}
|
|
395
|
+
const isFrozen = self.isFrozen;
|
|
396
|
+
const view = isFrozen ? self.frozenView : self.sharedMemView;
|
|
397
|
+
if (!view) {
|
|
398
|
+
for (let i = 0;i < keys.length; i++)
|
|
399
|
+
results[i] = null;
|
|
400
|
+
return results;
|
|
401
|
+
}
|
|
402
|
+
const bufOffset = isFrozen ? self.frozenBufOffset : self.liveReadBufOffset();
|
|
403
|
+
for (let i = 0;i < varInfos.length; i++) {
|
|
404
|
+
const info = varInfos[i];
|
|
405
|
+
if (info) {
|
|
406
|
+
results[i] = readVarValue(view, bufOffset + info.offset, info.type, info.count);
|
|
407
|
+
} else {
|
|
408
|
+
results[i] = null;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
return results;
|
|
412
|
+
}
|
|
413
|
+
};
|
|
258
414
|
}
|
|
259
415
|
freezeVarBufferLatest() {
|
|
260
|
-
this.unfreezeVarBufferLatest();
|
|
261
416
|
if (!this.header || !this.sharedMemBuffer)
|
|
262
417
|
return;
|
|
263
418
|
this.waitValidDataEvent();
|
|
264
|
-
const
|
|
419
|
+
const latestBufOffset = this.liveLatestBufOffset();
|
|
420
|
+
const bufLen = this.header.bufLen;
|
|
421
|
+
if (!this.frozenBuffer || this.frozenBufferCapacity < bufLen) {
|
|
422
|
+
this.frozenBuffer = new Uint8Array(bufLen);
|
|
423
|
+
this.frozenBufferCapacity = bufLen;
|
|
424
|
+
this.frozenView = new DataView(this.frozenBuffer.buffer);
|
|
425
|
+
}
|
|
426
|
+
this.frozenBuffer.set(new Uint8Array(this.sharedMemBuffer.buffer, latestBufOffset, bufLen));
|
|
265
427
|
this.frozenBufOffset = 0;
|
|
266
|
-
this.frozenBuffer = this.sharedMemBuffer.slice(varBuf.bufOffset, varBuf.bufOffset + this.header.bufLen);
|
|
267
|
-
this.frozenView = new DataView(this.frozenBuffer.buffer);
|
|
268
428
|
}
|
|
269
429
|
unfreezeVarBufferLatest() {
|
|
270
|
-
this.
|
|
271
|
-
|
|
272
|
-
|
|
430
|
+
this.frozenBufOffset = -1;
|
|
431
|
+
}
|
|
432
|
+
get isFrozen() {
|
|
433
|
+
return this.frozenBufOffset >= 0 && this.frozenView !== null;
|
|
273
434
|
}
|
|
274
435
|
async checkSimStatus() {
|
|
275
436
|
try {
|
|
@@ -288,11 +449,23 @@ class IRSDK {
|
|
|
288
449
|
}
|
|
289
450
|
return true;
|
|
290
451
|
}
|
|
452
|
+
static getSectionRegex(key) {
|
|
453
|
+
let regex = IRSDK.sessionRegexCache.get(key);
|
|
454
|
+
if (!regex) {
|
|
455
|
+
regex = new RegExp(`
|
|
456
|
+
${key}:
|
|
457
|
+
([\\s\\S]*?)(?=
|
|
458
|
+
|
|
459
|
+
|$)`);
|
|
460
|
+
IRSDK.sessionRegexCache.set(key, regex);
|
|
461
|
+
}
|
|
462
|
+
return regex;
|
|
463
|
+
}
|
|
291
464
|
getSessionInfo(key) {
|
|
292
465
|
if (!this.header || !this.sharedMemBuffer)
|
|
293
466
|
return null;
|
|
294
|
-
if (this.lastSessionInfoUpdate
|
|
295
|
-
this.lastSessionInfoUpdate = this.
|
|
467
|
+
if (this.lastSessionInfoUpdate !== this.sessionInfoUpdate) {
|
|
468
|
+
this.lastSessionInfoUpdate = this.sessionInfoUpdate;
|
|
296
469
|
for (const [k, v] of this.sessionInfoCache) {
|
|
297
470
|
if (v.data) {
|
|
298
471
|
this.sessionInfoCache.set(k, { ...v, data: null });
|
|
@@ -303,20 +476,16 @@ class IRSDK {
|
|
|
303
476
|
if (cached?.data) {
|
|
304
477
|
return cached.data;
|
|
305
478
|
}
|
|
306
|
-
const start = this.header.sessionInfoOffset;
|
|
307
|
-
const len = this.header.sessionInfoLen;
|
|
479
|
+
const start = this.liveHeaderInt(20, this.header.sessionInfoOffset);
|
|
480
|
+
const len = this.liveHeaderInt(16, this.header.sessionInfoLen);
|
|
308
481
|
const raw = readString(this.sharedMemBuffer, start, len);
|
|
309
|
-
const sectionMatch = raw.match(
|
|
310
|
-
${key}:
|
|
311
|
-
([\\s\\S]*?)(?=
|
|
312
|
-
|
|
313
|
-
|$)`));
|
|
482
|
+
const sectionMatch = raw.match(IRSDK.getSectionRegex(key));
|
|
314
483
|
if (!sectionMatch)
|
|
315
484
|
return null;
|
|
316
485
|
try {
|
|
317
486
|
const sectionYaml = `${key}:
|
|
318
487
|
${sectionMatch[1]}`;
|
|
319
|
-
const cleaned =
|
|
488
|
+
const cleaned = cleanYamlFast(sectionYaml);
|
|
320
489
|
const parsed = $parse(cleaned);
|
|
321
490
|
const result = parsed?.[key] ?? null;
|
|
322
491
|
this.sessionInfoCache.set(key, { data: result, update: this.lastSessionInfoUpdate });
|
|
@@ -325,15 +494,6 @@ ${sectionMatch[1]}`;
|
|
|
325
494
|
return null;
|
|
326
495
|
}
|
|
327
496
|
}
|
|
328
|
-
cleanYaml(yaml) {
|
|
329
|
-
let cleaned = yaml.replace(/[\x81\x8D\x8F\x90\x9D]/g, " ").replace(/[^\x09\x0A\x0D\x20-\x7E\xA0-\xFF]/g, "");
|
|
330
|
-
cleaned = cleaned.replace(/((?:DriverSetupName|UserName|TeamName|AbbrevName|Initials): )(.*)/g, (_, prefix, value) => {
|
|
331
|
-
const escaped = value.replace(/["\\]/g, "\\$&");
|
|
332
|
-
return `${prefix}"${escaped}"`;
|
|
333
|
-
});
|
|
334
|
-
cleaned = cleaned.replace(/(\w+: )(,.*)/g, '$1"$2"');
|
|
335
|
-
return cleaned;
|
|
336
|
-
}
|
|
337
497
|
getBroadcastMsgId() {
|
|
338
498
|
if (!user32)
|
|
339
499
|
return 0;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical lap point extraction — produces the per-sample telemetry series
|
|
3
|
+
* (byDist / byTime) that the UI charts and the backend lap_bundles store.
|
|
4
|
+
*
|
|
5
|
+
* Output shape matches the deployed IbtLapPoint contract exactly
|
|
6
|
+
* (UI/src/lib/ibt-extract.ts and BACKEND process-job convertToIbtLapData):
|
|
7
|
+
* - Speed m/s → km/h, Throttle/Brake 0–1 → 0–100 %, SteeringWheelAngle rad → deg
|
|
8
|
+
* - distanceKm normalized per lap: (LapDist − min LapDist) / 1000
|
|
9
|
+
* - timeSec normalized per lap: SessionTime − min SessionTime
|
|
10
|
+
* - tire temps LFtempM.. with LFtempCM.. carcass fallback (process-job behavior)
|
|
11
|
+
*
|
|
12
|
+
* CANONICAL DECISION — tire wear is percent 0–100 (×100 from the raw 0–1
|
|
13
|
+
* channel), matching process-job and therefore every stored lap bundle. The
|
|
14
|
+
* UI's ibt-extract.ts left wear at raw 0–1; that was the divergent one.
|
|
15
|
+
*
|
|
16
|
+
* RFC-0001 C8: extraction runs at FULL resolution; downsampling to maxPoints
|
|
17
|
+
* happens last and is presentation-only — never an input to metrics.
|
|
18
|
+
*
|
|
19
|
+
* RUNTIME NEUTRALITY: this module must not import Bun/Deno/Node APIs.
|
|
20
|
+
*/
|
|
21
|
+
import type { IbtSource, NormalizedLap } from './normalize';
|
|
22
|
+
/** Matches the UI/backend IbtLapPoint contract field-for-field. */
|
|
23
|
+
export interface LapPoint {
|
|
24
|
+
distanceKm: number;
|
|
25
|
+
timeSec: number;
|
|
26
|
+
speedKmh: number | null;
|
|
27
|
+
throttlePct: number | null;
|
|
28
|
+
brakePct: number | null;
|
|
29
|
+
brakeABSactive: boolean | null;
|
|
30
|
+
gear: number | null;
|
|
31
|
+
rpm: number | null;
|
|
32
|
+
steeringDeg: number | null;
|
|
33
|
+
lat: number | null;
|
|
34
|
+
lon: number | null;
|
|
35
|
+
altitudeM: number | null;
|
|
36
|
+
/** Lateral acceleration in m/s² (iRacing LatAccel). Added in 1.4.0. */
|
|
37
|
+
latAccelMs2: number | null;
|
|
38
|
+
/** Longitudinal acceleration in m/s² (iRacing LongAccel). Added in 1.4.0. */
|
|
39
|
+
longAccelMs2: number | null;
|
|
40
|
+
/** Yaw rate in deg/s (iRacing YawRate rad/s → deg/s). Added in 1.4.0. */
|
|
41
|
+
yawRateDegS: number | null;
|
|
42
|
+
tireTempLF: number | null;
|
|
43
|
+
tireTempRF: number | null;
|
|
44
|
+
tireTempLR: number | null;
|
|
45
|
+
tireTempRR: number | null;
|
|
46
|
+
tirePressureLF: number | null;
|
|
47
|
+
tirePressureRF: number | null;
|
|
48
|
+
tirePressureLR: number | null;
|
|
49
|
+
tirePressureRR: number | null;
|
|
50
|
+
tireWearLF: number | null;
|
|
51
|
+
tireWearRF: number | null;
|
|
52
|
+
tireWearLR: number | null;
|
|
53
|
+
tireWearRR: number | null;
|
|
54
|
+
}
|
|
55
|
+
export interface ExtractLapPointsResult {
|
|
56
|
+
/** Points sorted ascending by distanceKm (downsampled). */
|
|
57
|
+
byDist: LapPoint[];
|
|
58
|
+
/** Points sorted ascending by timeSec (downsampled). */
|
|
59
|
+
byTime: LapPoint[];
|
|
60
|
+
/** Number of valid full-resolution points before downsampling. */
|
|
61
|
+
downsampledFrom: number;
|
|
62
|
+
}
|
|
63
|
+
export interface ExtractLapPointsOptions {
|
|
64
|
+
/** Maximum points per series after downsampling. Default 500. */
|
|
65
|
+
maxPoints?: number;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Extract the telemetry point series for one normalized lap.
|
|
69
|
+
*
|
|
70
|
+
* Iterates lap.firstSampleIndex..lastSampleIndex; when the Lap / SessionNum
|
|
71
|
+
* channels are available, only indices whose Lap equals lap.lapNumber (and
|
|
72
|
+
* whose SessionNum matches the lap's own session, read at firstSampleIndex)
|
|
73
|
+
* are honored. Without those channels the whole contiguous range is accepted.
|
|
74
|
+
*/
|
|
75
|
+
export declare function extractLapPoints(source: IbtSource, lap: NormalizedLap, opts?: ExtractLapPointsOptions): ExtractLapPointsResult;
|
|
76
|
+
//# sourceMappingURL=lap-points.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lap-points.d.ts","sourceRoot":"","sources":["../src/lap-points.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAM5D,mEAAmE;AACnE,MAAM,WAAW,QAAQ;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,cAAc,EAAE,OAAO,GAAG,IAAI,CAAC;IAC/B,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,uEAAuE;IACvE,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,6EAA6E;IAC7E,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,yEAAyE;IACzE,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED,MAAM,WAAW,sBAAsB;IACrC,2DAA2D;IAC3D,MAAM,EAAE,QAAQ,EAAE,CAAC;IACnB,wDAAwD;IACxD,MAAM,EAAE,QAAQ,EAAE,CAAC;IACnB,kEAAkE;IAClE,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,uBAAuB;IACtC,iEAAiE;IACjE,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AA8CD;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,SAAS,EACjB,GAAG,EAAE,aAAa,EAClB,IAAI,CAAC,EAAE,uBAAuB,GAC7B,sBAAsB,CAqJxB"}
|
package/dist/live.d.ts
CHANGED
|
@@ -13,15 +13,23 @@ export declare class IRSDK {
|
|
|
13
13
|
private varHeaders;
|
|
14
14
|
private varHeadersNames;
|
|
15
15
|
private sessionInfoCache;
|
|
16
|
+
private static readonly sessionRegexCache;
|
|
16
17
|
private broadcastMsgId;
|
|
17
18
|
private lastSessionInfoUpdate;
|
|
18
19
|
private workaroundConnectedState;
|
|
19
20
|
private frozenBuffer;
|
|
20
21
|
private frozenView;
|
|
21
22
|
private frozenBufOffset;
|
|
23
|
+
private frozenBufferCapacity;
|
|
22
24
|
private testMode;
|
|
23
25
|
isInitialized: boolean;
|
|
24
26
|
constructor(options?: IRSDKOptions);
|
|
27
|
+
private liveHeaderInt;
|
|
28
|
+
private get liveStatus();
|
|
29
|
+
/** Tear-safe read target: the second-latest buffer by LIVE tick count. */
|
|
30
|
+
private liveReadBufOffset;
|
|
31
|
+
/** Freeze target: the newest buffer by LIVE tick count. */
|
|
32
|
+
private liveLatestBufOffset;
|
|
25
33
|
get isConnected(): boolean;
|
|
26
34
|
get sessionInfoUpdate(): number;
|
|
27
35
|
get varHeaderNames(): string[] | null;
|
|
@@ -29,12 +37,37 @@ export declare class IRSDK {
|
|
|
29
37
|
private startupFromFile;
|
|
30
38
|
shutdown(): void;
|
|
31
39
|
get(key: string): TelemetryValue | null;
|
|
40
|
+
/**
|
|
41
|
+
* Batch read multiple telemetry variables in a single pass.
|
|
42
|
+
* Much more efficient than calling get() multiple times.
|
|
43
|
+
* @param keys Array of variable names to read
|
|
44
|
+
* @returns Map of variable names to their values
|
|
45
|
+
*/
|
|
46
|
+
getMultiple(keys: string[]): Map<string, TelemetryValue>;
|
|
47
|
+
/**
|
|
48
|
+
* Batch read multiple telemetry variables into a pre-allocated object.
|
|
49
|
+
* Zero allocation in the hot path - fastest option for real-time telemetry.
|
|
50
|
+
* @param keys Array of variable names to read
|
|
51
|
+
* @param out Pre-allocated object to write values into
|
|
52
|
+
*/
|
|
53
|
+
getMultipleInto<T extends Record<string, TelemetryValue | null>>(keys: string[], out: T): T;
|
|
54
|
+
/**
|
|
55
|
+
* Creates a pre-compiled reader for maximum performance in hot loops.
|
|
56
|
+
* Pre-computes all offsets at creation time, avoiding Map lookups during reads.
|
|
57
|
+
* @param keys Array of variable names to read
|
|
58
|
+
* @returns A reader object with a read() method that returns values as an array
|
|
59
|
+
*/
|
|
60
|
+
createReader(keys: string[]): {
|
|
61
|
+
read: () => (TelemetryValue | null)[];
|
|
62
|
+
keys: string[];
|
|
63
|
+
};
|
|
32
64
|
freezeVarBufferLatest(): void;
|
|
33
65
|
unfreezeVarBufferLatest(): void;
|
|
66
|
+
private get isFrozen();
|
|
34
67
|
private checkSimStatus;
|
|
35
68
|
private waitValidDataEvent;
|
|
69
|
+
private static getSectionRegex;
|
|
36
70
|
getSessionInfo<T = unknown>(key: string): T | null;
|
|
37
|
-
private cleanYaml;
|
|
38
71
|
private getBroadcastMsgId;
|
|
39
72
|
private broadcastMsg;
|
|
40
73
|
private padCarNum;
|
package/dist/live.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"live.d.ts","sourceRoot":"","sources":["../src/live.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAqB,cAAc,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"live.d.ts","sourceRoot":"","sources":["../src/live.ts"],"names":[],"mappings":"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"}
|